♻️ Refactor code

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

View File

@@ -3,9 +3,11 @@
import * as ConfigService from "./configservice.js";
import * as DocumentService from "./documentservice.js";
import * as SystemService from "./systemservice.js";
export {
ConfigService,
DocumentService
DocumentService,
SystemService
};
export * from "./models.js";

View File

@@ -136,6 +136,73 @@ export enum EditType {
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
const $$createType0 = Edit.createFrom;
const $$createType1 = $Create.Array($$createType0);

View File

@@ -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;

View File

@@ -8,6 +8,7 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
MemoryMonitor: typeof import('./src/components/monitor/MemoryMonitor.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Toolbar: typeof import('./src/components/toolbar/Toolbar.vue')['default']

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,7 @@
"@codemirror/lang-python": "^6.2.1",
"@codemirror/lang-rust": "^6.0.1",
"@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-wast": "^6.0.2",
"@codemirror/lang-xml": "^6.1.0",
@@ -40,29 +40,30 @@
"@codemirror/lint": "^6.8.5",
"@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.36.8",
"@codemirror/view": "^6.37.1",
"@lezer/highlight": "^1.2.1",
"@types/uuid": "^10.0.0",
"@vueuse/core": "^13.2.0",
"@vueuse/core": "^13.3.0",
"codemirror": "^6.0.1",
"pinia": "^3.0.2",
"sass": "^1.89.0",
"pinia-plugin-persistedstate": "^4.3.0",
"sass": "^1.89.1",
"uuid": "^11.1.0",
"vue": "^3.5.14",
"vue-i18n": "^11.1.3",
"vue": "^3.5.16",
"vue-i18n": "^11.1.5",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@eslint/js": "^9.27.0",
"@types/node": "^22.15.18",
"@eslint/js": "^9.28.0",
"@types/node": "^22.15.29",
"@vitejs/plugin-vue": "^5.2.4",
"@wailsio/runtime": "latest",
"eslint": "^9.27.0",
"eslint": "^9.28.0",
"eslint-plugin-vue": "^10.1.0",
"globals": "^16.1.0",
"globals": "^16.2.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.32.1",
"unplugin-vue-components": "^28.5.0",
"typescript-eslint": "^8.33.0",
"unplugin-vue-components": "^28.7.0",
"vite": "^6.3.5",
"vue-eslint-parser": "^10.1.3",
"vue-tsc": "^2.2.10"

View File

@@ -1,5 +1,15 @@
<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>
<template>

View File

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

View File

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

View File

@@ -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);
}

View File

@@ -1,81 +1,17 @@
import {createI18n} from 'vue-i18n';
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实例
const i18n = createI18n({
compositionOnly: false,
globalInjection: true,
silentTranslationWarn: true,
locale: 'zh-CN',
fallbackLocale: 'zh-CN' as SupportedLocaleType,
fallbackLocale: 'zh-CN',
silentFallbackWarn: true,
missingWarn: true,
fallbackWarn: false,
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;

View File

@@ -48,6 +48,8 @@ export default {
},
settings: {
title: 'Settings',
backToEditor: 'Back to Editor',
systemInfo: 'System Info',
general: 'General',
editing: 'Editor',
appearance: 'Appearance',
@@ -56,6 +58,10 @@ export default {
comingSoon: 'Coming Soon...',
save: 'Save',
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',
enableGlobalHotkey: 'Enable Global Hotkeys',
window: 'Window/Application',
@@ -67,8 +73,8 @@ export default {
fontSize: 'Font Size',
fontSizeDescription: 'Editor font size',
fontSettings: 'Font Settings',
fontPreset: 'Font Preset',
fontPresetDescription: 'Choose a predefined font combination',
fontFamily: 'Font Family',
fontFamilyDescription: 'Choose editor font family',
fontWeight: 'Font Weight',
fontWeightDescription: 'Set the thickness of the font',
lineHeight: 'Line Height',

View File

@@ -48,6 +48,8 @@ export default {
},
settings: {
title: '设置',
backToEditor: '返回编辑器',
systemInfo: '系统信息',
general: '常规',
editing: '编辑器',
appearance: '外观',
@@ -56,6 +58,10 @@ export default {
comingSoon: '即将推出...',
save: '保存',
reset: '重置',
dangerZone: '危险操作',
resetAllSettings: '重置所有设置',
resetDescription: '这将恢复所有设置为默认值,此操作无法撤销',
confirmReset: '确定要重置所有设置吗?此操作无法撤销。',
globalHotkey: '全局键盘快捷键',
enableGlobalHotkey: '启用全局热键',
window: '窗口/应用程序',
@@ -67,8 +73,8 @@ export default {
fontSize: '字体大小',
fontSizeDescription: '编辑器字体大小',
fontSettings: '字体设置',
fontPreset: '字体预设',
fontPresetDescription: '选择预设的字体组合',
fontFamily: '字体',
fontFamilyDescription: '选择编辑器字体',
fontWeight: '字体粗细',
fontWeightDescription: '设置字体的粗细程度',
lineHeight: '行高',

View File

@@ -4,9 +4,10 @@ import '@/assets/styles/index.css';
import {createPinia} from 'pinia';
import i18n from './i18n';
import router from './router';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
const app = createApp(App);
app.use(pinia)
app.use(i18n);

View File

@@ -1,11 +1,11 @@
import {createRouter, createWebHashHistory, createWebHistory, RouteRecordRaw} from 'vue-router';
import Editor from '@/editor/Editor.vue';
import Settings from '@/settings/Settings.vue';
import GeneralPage from '@/settings/pages/GeneralPage.vue';
import EditingPage from '@/settings/pages/EditingPage.vue';
import AppearancePage from '@/settings/pages/AppearancePage.vue';
import KeyBindingsPage from '@/settings/pages/KeyBindingsPage.vue';
import UpdatesPage from '@/settings/pages/UpdatesPage.vue';
import Editor from '@/views/editor/Editor.vue';
import Settings from '@/views/settings/Settings.vue';
import GeneralPage from '@/views/settings/pages/GeneralPage.vue';
import EditingPage from '@/views/settings/pages/EditingPage.vue';
import AppearancePage from '@/views/settings/pages/AppearancePage.vue';
import KeyBindingsPage from '@/views/settings/pages/KeyBindingsPage.vue';
import UpdatesPage from '@/views/settings/pages/UpdatesPage.vue';
const routes: RouteRecordRaw[] = [
{

View File

@@ -1,5 +1,5 @@
import {defineStore} from 'pinia';
import {ref, computed} from 'vue';
import {computed, reactive} from 'vue';
import {
ConfigService
} from '@/../bindings/voidraft/internal/services';
@@ -7,10 +7,34 @@ import {EditorConfig, TabType, LanguageType} from '@/../bindings/voidraft/intern
import {useLogStore} from './logStore';
import { useI18n } from 'vue-i18n';
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',
fontFamily: 'editor.font_family',
fontWeight: 'editor.font_weight',
@@ -26,236 +50,257 @@ const CONFIG_KEY_MAP = {
const CONFIG_LIMITS = {
fontSize: { min: 12, max: 28, default: 13 },
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 }
} as const;
export const useConfigStore = defineStore('config', () => {
// 获取日志store
const logStore = useLogStore();
const { t } = useI18n();
// 常用字体选项
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 config = ref<EditorConfig>(new EditorConfig({
// 获取浏览器的默认语言
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: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
fontFamily: FONT_OPTIONS[0].value,
fontWeight: 'normal',
lineHeight: 1.5,
lineHeight: CONFIG_LIMITS.lineHeight.default,
enableTabIndent: true,
tabSize: CONFIG_LIMITS.tabSize.default,
tabType: CONFIG_LIMITS.tabType.default,
language: LanguageType.LangZhCN,
alwaysOnTop: false
}));
};
// 加载状态
const isLoading = ref(false);
const configLoaded = ref(false);
export const useConfigStore = defineStore('config', () => {
const logStore = useLogStore();
const { t, locale } = useI18n();
// 计算属性
const MIN_FONT_SIZE = computed(() => CONFIG_LIMITS.fontSize.min);
const MAX_FONT_SIZE = computed(() => CONFIG_LIMITS.fontSize.max);
const MIN_TAB_SIZE = computed(() => CONFIG_LIMITS.tabSize.min);
const MAX_TAB_SIZE = computed(() => CONFIG_LIMITS.tabSize.max);
// 响应式状态
const state = reactive({
config: { ...DEFAULT_CONFIG } as EditorConfig,
isLoading: false,
configLoaded: false
});
// 从后端加载配置
async function loadConfig(): Promise<void> {
if (isLoading.value) return;
// 计算属性 - 使用工厂函数简化
const createLimitComputed = (key: NumberConfigKey) => computed(() => CONFIG_LIMITS[key]);
const limits = {
fontSize: createLimitComputed('fontSize'),
tabSize: createLimitComputed('tabSize'),
lineHeight: createLimitComputed('lineHeight')
};
isLoading.value = true;
try {
const appConfig = await ConfigService.GetConfig();
// 错误处理装饰器
const withErrorHandling = <T extends any[], R>(
fn: (...args: T) => Promise<R>,
errorMsg: string,
successMsg?: string
) => async (...args: T): Promise<R | null> => {
const result = await fn(...args).catch(error => {
console.error(errorMsg, error);
logStore.error(t(errorMsg));
return null;
});
if (appConfig?.editor) {
Object.assign(config.value, appConfig.editor);
if (result !== null && successMsg) {
logStore.info(t(successMsg));
}
configLoaded.value = true;
logStore.info(t('config.loadSuccess'));
} catch (error) {
console.error('Failed to load configuration:', error);
logStore.error(t('config.loadFailed'));
} finally {
isLoading.value = false;
}
return result;
};
// 通用配置更新方法
const updateConfig = withErrorHandling(
async <K extends keyof EditorConfig>(key: K, value: EditorConfig[K]): Promise<void> => {
// 确保配置已加载
if (!state.configLoaded && !state.isLoading) {
await initConfig();
}
// 更新配置项的通用方法 - 直接调用后端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];
const backendKey = CONFIG_KEY_MAP[key];
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);
state.config[key] = value;
},
'config.saveFailed',
'config.saveSuccess'
);
// 更新本地状态
config.value[key] = value;
// 加载配置
const initConfig = withErrorHandling(
async (): Promise<void> => {
if (state.isLoading) return;
logStore.info(t('config.saveSuccess'));
} catch (error) {
console.error(`Failed to update config ${String(key)}:`, error);
logStore.error(t('config.saveFailed'));
throw error;
}
state.isLoading = true;
const appConfig = await ConfigService.GetConfig();
if (appConfig?.editor) {
Object.assign(state.config, appConfig.editor);
}
// 字体大小操作
async function adjustFontSize(delta: number): Promise<void> {
const newSize = ConfigUtils.clamp(config.value.fontSize + delta, CONFIG_LIMITS.fontSize.min, CONFIG_LIMITS.fontSize.max);
await updateConfig('fontSize', newSize);
}
state.configLoaded = true;
state.isLoading = false;
},
'config.loadFailed',
'config.loadSuccess'
);
// Tab大小操作
async function adjustTabSize(delta: number): Promise<void> {
const newSize = ConfigUtils.clamp(config.value.tabSize + delta, CONFIG_LIMITS.tabSize.min, CONFIG_LIMITS.tabSize.max);
await updateConfig('tabSize', newSize);
}
// 数值调整工厂函数
const createNumberAdjuster = (key: NumberConfigKey) => {
const limit = CONFIG_LIMITS[key];
return {
increase: () => updateConfig(key, ConfigUtils.clamp(state.config[key] + 1, limit.min, limit.max)),
decrease: () => updateConfig(key, ConfigUtils.clamp(state.config[key] - 1, limit.min, limit.max)),
set: (value: number) => updateConfig(key, ConfigUtils.clamp(value, limit.min, limit.max)),
reset: () => updateConfig(key, limit.default)
};
};
// 切换操作
async function toggleTabIndent(): Promise<void> {
await updateConfig('enableTabIndent', !config.value.enableTabIndent);
}
// 布尔值切换工厂函数
const createToggler = (key: BooleanConfigKey) =>
() => updateConfig(key, !state.config[key]);
async function toggleTabType(): Promise<void> {
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;
}
}
// 枚举值切换工厂函数
const createEnumToggler = <T extends TabType>(key: 'tabType', values: readonly T[]) =>
() => {
const currentIndex = values.indexOf(state.config[key] as T);
const nextIndex = (currentIndex + 1) % values.length;
return updateConfig(key, values[nextIndex]);
};
// 重置配置
async function resetConfig(): Promise<void> {
if (isLoading.value) return;
const resetConfig = withErrorHandling(
async (): Promise<void> => {
if (state.isLoading) return;
try {
isLoading.value = true;
state.isLoading = 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;
}
}
await initConfig();
state.isLoading = false;
},
'config.resetFailed',
'config.resetSuccess'
);
// 设置字体大小
async function setFontSize(size: number): Promise<void> {
await updateConfig('fontSize', size);
}
// 语言设置方法
const setLanguage = withErrorHandling(
async (language: LanguageType): Promise<void> => {
await ConfigService.Set(CONFIG_KEY_MAP.language, language);
state.config.language = language;
// 设置Tab大小
async function setTabSize(size: number): Promise<void> {
await updateConfig('tabSize', size);
}
// 同步更新前端语言
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language);
locale.value = frontendLocale as any;
},
'config.languageChangeFailed',
'config.languageChanged'
);
// 字体预设相关方法
async function setFontPreset(presetKey: FontPresetKey): Promise<void> {
const preset = FONT_PRESETS[presetKey];
if (!preset) {
throw new Error(`Unknown font preset: ${presetKey}`);
}
// 通过前端语言代码设置语言
const setLocale = async (localeCode: SupportedLocaleType): Promise<void> => {
const backendLanguage = ConfigUtils.frontendLanguageToBackend(localeCode);
await setLanguage(backendLanguage);
};
// 初始化语言设置
const initializeLanguage = async (): Promise<void> => {
try {
// 批量更新字体相关配置
await updateConfig('fontFamily', preset.fontFamily);
await updateConfig('fontWeight', preset.fontWeight);
await updateConfig('lineHeight', preset.lineHeight);
// 可选择是否同时更新字体大小
// await updateConfig('fontSize', preset.fontSize);
// 如果配置未加载,先加载配置
if (!state.configLoaded) {
await initConfig();
}
logStore.info(`字体预设已切换为: ${preset.name}`);
// 同步前端语言设置
const frontendLocale = ConfigUtils.backendLanguageToFrontend(state.config.language);
locale.value = frontendLocale as any;
} catch (error) {
console.error('Failed to set font preset:', error);
logStore.error('字体预设设置失败');
throw error;
}
const browserLang = getBrowserLanguage();
locale.value = browserLang as any;
}
};
// 获取当前字体预设(如果匹配的话)
function getCurrentFontPreset(): FontPresetKey | null {
const currentFamily = config.value.fontFamily;
for (const [key, preset] of Object.entries(FONT_PRESETS)) {
if (preset.fontFamily === currentFamily) {
return key as FontPresetKey;
}
}
return null;
}
// 创建数值调整器
const fontSize = createNumberAdjuster('fontSize');
const tabSize = createNumberAdjuster('tabSize');
const lineHeight = createNumberAdjuster('lineHeight');
// 设置字体族
async function setFontFamily(fontFamily: string): Promise<void> {
await updateConfig('fontFamily', fontFamily);
}
// 创建切换器
const toggleTabIndent = createToggler('enableTabIndent');
const toggleAlwaysOnTop = createToggler('alwaysOnTop');
const toggleTabType = createEnumToggler('tabType', CONFIG_LIMITS.tabType.values);
// 设置字体粗细
async function setFontWeight(fontWeight: string): Promise<void> {
await updateConfig('fontWeight', fontWeight);
}
// 设置行高
async function setLineHeight(lineHeight: number): Promise<void> {
await updateConfig('lineHeight', lineHeight);
}
// 字符串配置设置器
const setFontFamily = (value: string) => updateConfig('fontFamily', value);
const setFontWeight = (value: string) => updateConfig('fontWeight', value);
return {
// 状态
config,
configLoaded,
isLoading,
config: computed(() => state.config),
configLoaded: computed(() => state.configLoaded),
isLoading: computed(() => state.isLoading),
// 计算属性
MIN_FONT_SIZE,
MAX_FONT_SIZE,
MIN_TAB_SIZE,
MAX_TAB_SIZE,
// 限制常量
...limits,
// 核心方法
loadConfig,
initConfig,
resetConfig,
setLanguage,
updateConfig,
// 语言相关方法
setLanguage,
setLocale,
initializeLanguage,
// 字体大小操作
increaseFontSize: () => adjustFontSize(1),
decreaseFontSize: () => adjustFontSize(-1),
resetFontSize: () => setFontSize(CONFIG_LIMITS.fontSize.default),
setFontSize,
...fontSize,
increaseFontSize: fontSize.increase,
decreaseFontSize: fontSize.decrease,
resetFontSize: fontSize.reset,
setFontSize: fontSize.set,
// Tab操作
toggleTabIndent,
increaseTabSize: () => adjustTabSize(1),
decreaseTabSize: () => adjustTabSize(-1),
...tabSize,
increaseTabSize: tabSize.increase,
decreaseTabSize: tabSize.decrease,
setTabSize: tabSize.set,
toggleTabType,
setTabSize,
// 行高操作
setLineHeight: lineHeight.set,
// 窗口操作
toggleAlwaysOnTop,
// 字体预设相关方法
setFontPreset,
getCurrentFontPreset,
// 字体操作
setFontFamily,
setFontWeight,
setLineHeight
};
},{
persist: true,
});

View File

@@ -1,5 +1,5 @@
import { LanguageType } from '@/../bindings/voidraft/internal/models/models';
import { SupportedLocaleType } from '@/i18n';
import type { SupportedLocaleType } from '@/stores/configStore';
/**
* 配置工具类

View File

@@ -19,7 +19,7 @@ import {
updateFontConfig,
} from './extensions';
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';
const editorStore = useEditorStore();

View File

@@ -1,5 +1,5 @@
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';
// 定义自动保存配置选项

View File

@@ -23,7 +23,7 @@ import {defaultKeymap, history, historyKeymap,} from '@codemirror/commands';
import {highlightSelectionMatches, searchKeymap} from '@codemirror/search';
import {autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap} from '@codemirror/autocomplete';
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[] => {

View File

@@ -0,0 +1,111 @@
import { EditorView } from '@codemirror/view';
import { Extension, Compartment } from '@codemirror/state';
// 字体配置接口
export interface FontConfig {
fontFamily: string;
fontSize?: number;
lineHeight?: number;
fontWeight?: string;
}
// 创建字体配置compartment
export const fontCompartment = new Compartment();
// 默认字体配置
export const DEFAULT_FONT_CONFIG: FontConfig = {
fontFamily: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
fontSize: 14,
lineHeight: 1.5,
fontWeight: 'normal'
};
// 从后端配置创建字体配置
export function createFontConfigFromBackend(backendConfig: {
fontFamily?: string;
fontSize?: number;
lineHeight?: number;
fontWeight?: string;
}): FontConfig {
return {
fontFamily: backendConfig.fontFamily || DEFAULT_FONT_CONFIG.fontFamily,
fontSize: backendConfig.fontSize || DEFAULT_FONT_CONFIG.fontSize,
lineHeight: backendConfig.lineHeight || DEFAULT_FONT_CONFIG.lineHeight,
fontWeight: backendConfig.fontWeight || DEFAULT_FONT_CONFIG.fontWeight,
};
}
// 创建字体样式扩展
export function createFontExtension(config: Partial<FontConfig> = {}): Extension {
const fontConfig = { ...DEFAULT_FONT_CONFIG, ...config };
const styles: Record<string, any> = {
'&': {
fontFamily: fontConfig.fontFamily,
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
...(fontConfig.lineHeight && { lineHeight: fontConfig.lineHeight.toString() }),
...(fontConfig.fontWeight && { fontWeight: fontConfig.fontWeight }),
},
'.cm-content': {
fontFamily: fontConfig.fontFamily,
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
...(fontConfig.lineHeight && { lineHeight: fontConfig.lineHeight.toString() }),
...(fontConfig.fontWeight && { fontWeight: fontConfig.fontWeight }),
},
'.cm-editor': {
fontFamily: fontConfig.fontFamily,
},
'.cm-scroller': {
fontFamily: fontConfig.fontFamily,
},
'.cm-gutters': {
fontFamily: fontConfig.fontFamily,
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
},
'.cm-lineNumbers': {
fontFamily: fontConfig.fontFamily,
...(fontConfig.fontSize && { fontSize: `${Math.max(10, fontConfig.fontSize - 1)}px` }),
},
'.cm-tooltip': {
fontFamily: fontConfig.fontFamily,
...(fontConfig.fontSize && { fontSize: `${Math.max(12, fontConfig.fontSize - 1)}px` }),
},
'.cm-completionLabel': {
fontFamily: fontConfig.fontFamily,
},
'.cm-completionDetail': {
fontFamily: fontConfig.fontFamily,
}
};
return EditorView.theme(styles);
}
// 创建响应式字体大小扩展
export function createResponsiveFontExtension(baseFontSize: number = 14): Extension {
return fontCompartment.of(createFontExtension({
fontSize: baseFontSize,
lineHeight: 1.5
}));
}
// 从后端配置创建字体扩展
export function createFontExtensionFromBackend(backendConfig: {
fontFamily?: string;
fontSize?: number;
lineHeight?: number;
fontWeight?: string;
}): Extension {
const fontConfig = createFontConfigFromBackend(backendConfig);
return fontCompartment.of(createFontExtension(fontConfig));
}
// 动态更新字体配置
export function updateFontConfig(view: EditorView, config: Partial<FontConfig>): void {
const newFontExtension = createFontExtension(config);
// 使用compartment重新配置字体扩展
view.dispatch({
effects: fontCompartment.reconfigure(newFontExtension)
});
}

View File

@@ -2,7 +2,7 @@ import {Compartment, Extension} from '@codemirror/state';
import {EditorView, keymap} from '@codemirror/view';
import {indentSelection} from '@codemirror/commands';
import {indentUnit} from '@codemirror/language';
import {TabType} from '@/../bindings/voidraft/internal/models/models';
import {TabType} from '../../../../bindings/voidraft/internal/models/models';
// Tab设置相关的compartment
export const tabSizeCompartment = new Compartment();

View File

@@ -3,6 +3,7 @@ import { useConfigStore } from '@/stores/configStore';
import { useI18n } from 'vue-i18n';
import { ref, watch } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import MemoryMonitor from '@/components/monitor/MemoryMonitor.vue';
const { t } = useI18n();
const configStore = useConfigStore();
@@ -26,9 +27,9 @@ const handleNavClick = (item: typeof navItems[0]) => {
router.push(item.route);
};
//
const resetSettings = async () => {
await configStore.resetConfig();
//
const goBackToEditor = () => {
router.push('/');
};
</script>
@@ -36,8 +37,15 @@ const resetSettings = async () => {
<div class="settings-container">
<div class="settings-sidebar">
<div class="settings-header">
<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 class="settings-nav">
<div
v-for="item in navItems"
@@ -50,10 +58,11 @@ const resetSettings = async () => {
<span class="nav-text">{{ t(`settings.${item.id}`) }}</span>
</div>
</div>
<div class="settings-actions">
<button class="reset-button" @click="resetSettings">
{{ t('settings.reset') }}
</button>
<div class="settings-footer">
<div class="memory-info-section">
<div class="section-title">{{ t('settings.systemInfo') }}</div>
<MemoryMonitor />
</div>
</div>
</div>
<div class="settings-content">
@@ -88,6 +97,34 @@ const resetSettings = async () => {
border-bottom: 1px solid #444444;
background-color: #2d2d2d;
.header-content {
display: flex;
align-items: center;
gap: 12px;
.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;
@@ -95,6 +132,7 @@ const resetSettings = async () => {
color: #ffffff;
}
}
}
.settings-nav {
flex: 1;
@@ -144,29 +182,23 @@ const resetSettings = async () => {
}
}
.settings-actions {
padding: 16px;
.settings-footer {
padding: 12px 16px 16px 16px;
border-top: 1px solid #444444;
background-color: #2d2d2d;
.reset-button {
width: 100%;
padding: 8px 12px;
background-color: #3a3a3a;
border: 1px solid #555555;
color: #e0e0e0;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 14px;
.memory-info-section {
display: flex;
flex-direction: column;
gap: 8px;
&:hover {
background-color: #444444;
border-color: #666666;
}
&:active {
transform: translateY(1px);
.section-title {
font-size: 10px;
color: #777777;
font-weight: 500;
margin-bottom: 0;
text-transform: uppercase;
letter-spacing: 0.5px;
}
}
}

View File

@@ -4,9 +4,7 @@ import { useI18n } from 'vue-i18n';
import { ref } from 'vue';
import SettingSection from '../components/SettingSection.vue';
import SettingItem from '../components/SettingItem.vue';
import { LanguageType } from '@/../bindings/voidraft/internal/models/models';
import { setLocale, SupportedLocaleType } from '@/i18n';
import { ConfigUtils } from '@/utils/configUtils';
import { LanguageType } from '../../../../bindings/voidraft/internal/models/models';
const { t } = useI18n();
const configStore = useConfigStore();
@@ -23,12 +21,8 @@ const updateLanguage = async (event: Event) => {
const selectedLanguage = select.value as LanguageType;
try {
//
// 使 configStore
await configStore.setLanguage(selectedLanguage);
//
const frontendLocale = ConfigUtils.backendLanguageToFrontend(selectedLanguage);
setLocale(frontendLocale);
} catch (error) {
console.error('Failed to update language:', error);
}

View File

@@ -1,26 +1,37 @@
<script setup lang="ts">
import { useConfigStore } from '@/stores/configStore';
import { FONT_OPTIONS } from '@/stores/configStore';
import { useI18n } from 'vue-i18n';
import { ref, computed } from 'vue';
import { ref, computed, onMounted } from 'vue';
import SettingSection from '../components/SettingSection.vue';
import SettingItem from '../components/SettingItem.vue';
import ToggleSwitch from '../components/ToggleSwitch.vue';
import { TabType } from '@/../bindings/voidraft/internal/models/models';
import { getFontPresetOptions, type FontPresetKey } from '@/editor/extensions/fontExtension';
import { TabType } from '../../../../bindings/voidraft/internal/models/models';
const { t } = useI18n();
const configStore = useConfigStore();
//
const fontPresetOptions = getFontPresetOptions();
const currentFontPreset = computed(() => configStore.getCurrentFontPreset());
//
onMounted(async () => {
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 presetKey = target.value;
if (presetKey && presetKey !== 'custom') {
configStore.setFontPreset(presetKey as FontPresetKey);
const fontFamily = target.value;
if (fontFamily) {
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;
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);
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);
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 = () => {
configStore.increaseFontSize();
const increaseFontSize = async () => {
try {
await configStore.increaseFontSize();
} catch (error) {
console.error('Failed to increase font size:', error);
}
};
const decreaseFontSize = () => {
configStore.decreaseFontSize();
const decreaseFontSize = async () => {
try {
await configStore.decreaseFontSize();
} catch (error) {
console.error('Failed to decrease font size:', error);
}
};
// Tab
@@ -71,12 +102,37 @@ const tabTypeText = computed(() => {
});
// Tab
const increaseTabSize = () => {
configStore.increaseTabSize();
const increaseTabSize = async () => {
try {
await configStore.increaseTabSize();
} catch (error) {
console.error('Failed to increase tab size:', error);
}
};
const decreaseTabSize = () => {
configStore.decreaseTabSize();
const decreaseTabSize = async () => {
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>
@@ -84,17 +140,16 @@ const decreaseTabSize = () => {
<div class="settings-page">
<SettingSection :title="t('settings.fontSettings')">
<SettingItem
:title="t('settings.fontPreset')"
:description="t('settings.fontPresetDescription')"
:title="t('settings.fontFamily')"
:description="t('settings.fontFamilyDescription')"
>
<select
class="font-preset-select"
:value="currentFontPreset || 'custom'"
@change="handleFontPresetChange"
class="font-family-select"
:value="currentFontFamily"
@change="handleFontFamilyChange"
>
<option value="custom">自定义</option>
<option
v-for="option in fontPresetOptions"
v-for="option in fontFamilyOptions"
:key="option.value"
:value="option.value"
>
@@ -163,14 +218,14 @@ const decreaseTabSize = () => {
<SettingSection :title="t('settings.tabSettings')">
<SettingItem :title="t('settings.tabSize')">
<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>
<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>
</SettingItem>
<SettingItem :title="t('settings.tabType')">
<button class="tab-type-toggle" @click="configStore.toggleTabType">
<button class="tab-type-toggle" @click="handleToggleTabType">
{{ tabTypeText }}
</button>
</SettingItem>
@@ -178,7 +233,7 @@ const decreaseTabSize = () => {
<SettingItem :title="t('settings.enableTabIndent')">
<ToggleSwitch
v-model="configStore.config.enableTabIndent"
@update:modelValue="configStore.toggleTabIndent"
@update:modelValue="handleToggleTabIndent"
/>
</SettingItem>
</SettingSection>
@@ -325,7 +380,7 @@ const decreaseTabSize = () => {
}
}
.font-preset-select,
.font-family-select,
.font-weight-select {
min-width: 180px;
padding: 8px 12px;

View File

@@ -39,6 +39,13 @@ const updateSelectedKey = (event: Event) => {
const toggleModifier = (key: keyof typeof selectedModifiers.value) => {
selectedModifiers.value[key] = !selectedModifiers.value[key];
};
//
const resetSettings = async () => {
if (confirm(t('settings.confirmReset'))) {
await configStore.resetConfig();
}
};
</script>
<template>
@@ -96,6 +103,20 @@ const toggleModifier = (key: keyof typeof selectedModifiers.value) => {
<button class="select-button">{{ t('settings.selectDirectory') }}</button>
</div>
</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>
</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>

6
go.mod
View File

@@ -13,12 +13,12 @@ require (
require (
dario.cat/mergo v1.0.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/bep/debounce v1.2.1 // indirect
github.com/cloudflare/circl v1.6.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/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // 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/leaanthony/go-ansi-parser v1.6.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-isatty v0.0.20 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect

12
go.sum
View File

@@ -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.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
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.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
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/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
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/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
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/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
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.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/lmittmann/tint v1.1.1 h1:xmmGuinUsCSxWdwH1OqMUQ4tzQsq3BdjJLAAmVKJ9Dw=
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/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=

View File

@@ -8,7 +8,7 @@ import (
)
// 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)
@@ -28,30 +28,14 @@ func RegisterTrayEvents(app *application.App, systray *application.SystemTray, m
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 注册系统托盘菜单事件
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) {
mainWindow.Show()
})
menu.Add("设置").OnClick(func(data *application.Context) {
settingsWindow.Show()
})
menu.AddSeparator()
menu.Add("退出").OnClick(func(data *application.Context) {

View File

@@ -9,6 +9,7 @@ import (
type ServiceManager struct {
configService *ConfigService
documentService *DocumentService
systemService *SystemService
logger *log.LoggerService
}
@@ -23,6 +24,9 @@ func NewServiceManager() *ServiceManager {
// 初始化文档服务
documentService := NewDocumentService(configService, logger)
// 初始化系统服务
systemService := NewSystemService(logger)
// 初始化文档服务
err := documentService.Initialize()
if err != nil {
@@ -33,6 +37,7 @@ func NewServiceManager() *ServiceManager {
return &ServiceManager{
configService: configService,
documentService: documentService,
systemService: systemService,
logger: logger,
}
}
@@ -42,5 +47,6 @@ func (sm *ServiceManager) GetServices() []application.Service {
return []application.Service{
application.NewService(sm.configService),
application.NewService(sm.documentService),
application.NewService(sm.systemService),
}
}

View File

@@ -0,0 +1,83 @@
package services
import (
"fmt"
"runtime"
"github.com/wailsapp/wails/v3/pkg/services/log"
)
// SystemService 系统监控服务
type SystemService struct {
logger *log.LoggerService
}
// MemoryStats 内存统计信息
type MemoryStats struct {
// 当前堆内存使用量(字节)
HeapInUse uint64 `json:"heapInUse"`
// 堆内存分配总量(字节)
HeapAlloc uint64 `json:"heapAlloc"`
// 系统内存使用量(字节)
Sys uint64 `json:"sys"`
// GC 次数
NumGC uint32 `json:"numGC"`
// GC 暂停时间(纳秒)
PauseTotalNs uint64 `json:"pauseTotalNs"`
// Goroutine 数量
NumGoroutine int `json:"numGoroutine"`
}
// NewSystemService 创建新的系统服务实例
func NewSystemService(logger *log.LoggerService) *SystemService {
return &SystemService{
logger: logger,
}
}
// GetMemoryStats 获取当前内存统计信息
func (ss *SystemService) GetMemoryStats() MemoryStats {
var m runtime.MemStats
runtime.ReadMemStats(&m)
return MemoryStats{
HeapInUse: m.HeapInuse,
HeapAlloc: m.HeapAlloc,
Sys: m.Sys,
NumGC: m.NumGC,
PauseTotalNs: m.PauseTotalNs,
NumGoroutine: runtime.NumGoroutine(),
}
}
// FormatBytes 格式化字节数为人类可读的格式
func (ss *SystemService) FormatBytes(bytes uint64) string {
const unit = 1024
if bytes < unit {
return "< 1 KB"
}
div, exp := uint64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
switch exp {
case 0:
return "< 1 KB"
case 1:
return fmt.Sprintf("%.1f KB", float64(bytes)/float64(div))
case 2:
return fmt.Sprintf("%.1f MB", float64(bytes)/float64(div))
case 3:
return fmt.Sprintf("%.1f GB", float64(bytes)/float64(div))
default:
return fmt.Sprintf("%.1f TB", float64(bytes)/float64(div))
}
}
// TriggerGC 手动触发垃圾回收
func (ss *SystemService) TriggerGC() {
runtime.GC()
ss.logger.Info("Manual GC triggered")
}

View File

@@ -7,7 +7,7 @@ import (
)
// 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()
@@ -22,10 +22,10 @@ func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow
menu := app.NewMenu()
// 注册托盘菜单事件
events.RegisterTrayMenuEvents(app, menu, mainWindow, settingsWindow)
events.RegisterTrayMenuEvents(app, menu, mainWindow)
systray.SetMenu(menu)
// 注册托盘相关事件
events.RegisterTrayEvents(app, systray, mainWindow, settingsWindow)
events.RegisterTrayEvents(app, systray, mainWindow)
}

21
main.go
View File

@@ -88,28 +88,9 @@ func main() {
URL: "/#/",
})
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.
// The frontend can listen to this event and update the UI accordingly.