✨ Added settings page
This commit is contained in:
@@ -6,12 +6,12 @@ version: '3'
|
|||||||
# This information is used to generate the build assets.
|
# This information is used to generate the build assets.
|
||||||
info:
|
info:
|
||||||
companyName: "Voidraft" # The name of the company
|
companyName: "Voidraft" # The name of the company
|
||||||
productName: "Voidraftt" # The name of the application
|
productName: "Voidraft" # The name of the application
|
||||||
productIdentifier: "landaiqing" # The unique product identifier
|
productIdentifier: "landaiqing" # The unique product identifier
|
||||||
description: "Your Inspiration Catcher - Instant thought-capturing tool with minimalist design" # The application description
|
description: "Your Inspiration Catcher - Instant thought-capturing tool with minimalist design" # The application description
|
||||||
copyright: "© 2025 Voidraft. All rights reserved." # Copyright text
|
copyright: "© 2025 Voidraft. All rights reserved." # Copyright text
|
||||||
comments: "Effortlessly capture and organize fleeting ideas with minimal design" # Comments
|
comments: "Effortlessly capture and organize fleeting ideas with minimal design" # Comments
|
||||||
version: "v0.0.1" # The application version
|
version: "0.0.1.0" # The application version
|
||||||
|
|
||||||
# Dev mode configuration
|
# Dev mode configuration
|
||||||
dev_mode:
|
dev_mode:
|
||||||
|
@@ -4,17 +4,17 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>My Product</string>
|
<string>Voidraft</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>voidraft.exe</string>
|
<string>voidraft</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.wails.voidraft</string>
|
<string>landaiqing</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>0.1.0</string>
|
<string>0.0.1.0</string>
|
||||||
<key>CFBundleGetInfoString</key>
|
<key>CFBundleGetInfoString</key>
|
||||||
<string>This is a comment</string>
|
<string>Effortlessly capture and organize fleeting ideas with minimal design</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.1.0</string>
|
<string>0.0.1.0</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>icons</string>
|
<string>icons</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<string>true</string>
|
<string>true</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>© now, My Company</string>
|
<string>© 2025 Voidraft. All rights reserved.</string>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsLocalNetworking</key>
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
@@ -4,17 +4,17 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>My Product</string>
|
<string>Voidraft</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>voidraft.exe</string>
|
<string>voidraft</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.wails.voidraft</string>
|
<string>landaiqing</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>0.1.0</string>
|
<string>0.0.1.0</string>
|
||||||
<key>CFBundleGetInfoString</key>
|
<key>CFBundleGetInfoString</key>
|
||||||
<string>This is a comment</string>
|
<string>Effortlessly capture and organize fleeting ideas with minimal design</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.1.0</string>
|
<string>0.0.1.0</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>icons</string>
|
<string>icons</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
@@ -22,6 +22,6 @@
|
|||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<string>true</string>
|
<string>true</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>© now, My Company</string>
|
<string>© 2025 Voidraft. All rights reserved.</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
@@ -3,26 +3,26 @@
|
|||||||
#
|
#
|
||||||
# The lines below are called `modelines`. See `:help modeline`
|
# The lines below are called `modelines`. See `:help modeline`
|
||||||
|
|
||||||
name: "voidraft.exe"
|
name: "voidraft"
|
||||||
arch: ${GOARCH}
|
arch: ${GOARCH}
|
||||||
platform: "linux"
|
platform: "linux"
|
||||||
version: "0.1.0"
|
version: "0.0.1.0"
|
||||||
section: "default"
|
section: "default"
|
||||||
priority: "extra"
|
priority: "extra"
|
||||||
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
|
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
|
||||||
description: "Voidraft: Your Inspiration Catcher - Instant thought-capturing tool with minimalist design"
|
description: "Your Inspiration Catcher - Instant thought-capturing tool with minimalist design"
|
||||||
vendor: "Voidraft"
|
vendor: "Voidraft"
|
||||||
homepage: "https://voidraft.app"
|
homepage: "https://wails.io"
|
||||||
license: "MIT"
|
license: "MIT"
|
||||||
release: "1"
|
release: "1"
|
||||||
|
|
||||||
contents:
|
contents:
|
||||||
- src: "./bin/voidraft.exe"
|
- src: "./bin/voidraft"
|
||||||
dst: "/usr/local/bin/voidraft.exe"
|
dst: "/usr/local/bin/voidraft"
|
||||||
- src: "./build/appicon.png"
|
- src: "./build/appicon.png"
|
||||||
dst: "/usr/share/icons/hicolor/128x128/apps/voidraft.exe.png"
|
dst: "/usr/share/icons/hicolor/128x128/apps/voidraft.png"
|
||||||
- src: "./build/linux/voidraft.exe.desktop"
|
- src: "./build/linux/voidraft.desktop"
|
||||||
dst: "/usr/share/applications/voidraft.exe.desktop"
|
dst: "/usr/share/applications/voidraft.desktop"
|
||||||
|
|
||||||
depends:
|
depends:
|
||||||
- gtk3
|
- gtk3
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"fixed": {
|
"fixed": {
|
||||||
"file_version": "0.1.0"
|
"file_version": "0.0.1.0"
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"0000": {
|
"0000": {
|
||||||
"ProductVersion": "0.1.0",
|
"ProductVersion": "0.0.1.0",
|
||||||
"CompanyName": "Voidraft",
|
"CompanyName": "Voidraft",
|
||||||
"FileDescription": "Voidraft: Your Inspiration Catcher - Instant thought-capturing tool with minimalist design",
|
"FileDescription": "Your Inspiration Catcher - Instant thought-capturing tool with minimalist design",
|
||||||
"LegalCopyright": "© 2025 Voidraft. All rights reserved.",
|
"LegalCopyright": "© 2025 Voidraft. All rights reserved.",
|
||||||
"ProductName": "Voidraft",
|
"ProductName": "Voidraft",
|
||||||
"Comments": "Effortlessly capture and organize fleeting ideas with minimal design"
|
"Comments": "Effortlessly capture and organize fleeting ideas with minimal design"
|
||||||
|
@@ -8,16 +8,16 @@
|
|||||||
!define INFO_PROJECTNAME "voidraft"
|
!define INFO_PROJECTNAME "voidraft"
|
||||||
!endif
|
!endif
|
||||||
!ifndef INFO_COMPANYNAME
|
!ifndef INFO_COMPANYNAME
|
||||||
!define INFO_COMPANYNAME "My Company"
|
!define INFO_COMPANYNAME "Voidraft"
|
||||||
!endif
|
!endif
|
||||||
!ifndef INFO_PRODUCTNAME
|
!ifndef INFO_PRODUCTNAME
|
||||||
!define INFO_PRODUCTNAME "My Product"
|
!define INFO_PRODUCTNAME "Voidraft"
|
||||||
!endif
|
!endif
|
||||||
!ifndef INFO_PRODUCTVERSION
|
!ifndef INFO_PRODUCTVERSION
|
||||||
!define INFO_PRODUCTVERSION "0.1.0"
|
!define INFO_PRODUCTVERSION "0.0.1.0"
|
||||||
!endif
|
!endif
|
||||||
!ifndef INFO_COPYRIGHT
|
!ifndef INFO_COPYRIGHT
|
||||||
!define INFO_COPYRIGHT "© now, My Company"
|
!define INFO_COPYRIGHT "© 2025 Voidraft. All rights reserved."
|
||||||
!endif
|
!endif
|
||||||
!ifndef PRODUCT_EXECUTABLE
|
!ifndef PRODUCT_EXECUTABLE
|
||||||
!define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
|
!define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
<assemblyIdentity type="win32" name="com.wails.voidraft" version="0.1.0" processorArchitecture="*"/>
|
<assemblyIdentity type="win32" name="landaiqing" version="0.0.1.0" processorArchitecture="*"/>
|
||||||
<dependency>
|
<dependency>
|
||||||
<dependentAssembly>
|
<dependentAssembly>
|
||||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
|
1331
frontend/package-lock.json
generated
1331
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -20,13 +20,13 @@
|
|||||||
"@codemirror/lang-go": "^6.0.1",
|
"@codemirror/lang-go": "^6.0.1",
|
||||||
"@codemirror/lang-html": "^6.4.9",
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
"@codemirror/lang-java": "^6.0.1",
|
"@codemirror/lang-java": "^6.0.1",
|
||||||
"@codemirror/lang-javascript": "^6.2.3",
|
"@codemirror/lang-javascript": "^6.2.4",
|
||||||
"@codemirror/lang-json": "^6.0.1",
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
"@codemirror/lang-less": "^6.0.2",
|
"@codemirror/lang-less": "^6.0.2",
|
||||||
"@codemirror/lang-liquid": "^6.2.3",
|
"@codemirror/lang-liquid": "^6.2.3",
|
||||||
"@codemirror/lang-markdown": "^6.3.2",
|
"@codemirror/lang-markdown": "^6.3.2",
|
||||||
"@codemirror/lang-php": "^6.0.1",
|
"@codemirror/lang-php": "^6.0.1",
|
||||||
"@codemirror/lang-python": "^6.2.0",
|
"@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.8.0",
|
||||||
@@ -38,30 +38,30 @@
|
|||||||
"@codemirror/language-data": "^6.5.1",
|
"@codemirror/language-data": "^6.5.1",
|
||||||
"@codemirror/legacy-modes": "^6.5.1",
|
"@codemirror/legacy-modes": "^6.5.1",
|
||||||
"@codemirror/lint": "^6.8.5",
|
"@codemirror/lint": "^6.8.5",
|
||||||
"@codemirror/search": "^6.5.10",
|
"@codemirror/search": "^6.5.11",
|
||||||
"@codemirror/state": "^6.5.2",
|
"@codemirror/state": "^6.5.2",
|
||||||
"@codemirror/view": "^6.36.7",
|
"@codemirror/view": "^6.36.8",
|
||||||
"@lezer/highlight": "^1.2.1",
|
"@lezer/highlight": "^1.2.1",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@vueuse/core": "^13.1.0",
|
"@vueuse/core": "^13.2.0",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
"sass": "^1.88.0",
|
"sass": "^1.89.0",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.14",
|
||||||
"vue-i18n": "^11.1.3",
|
"vue-i18n": "^11.1.3",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.26.0",
|
"@eslint/js": "^9.27.0",
|
||||||
"@types/node": "^22.15.17",
|
"@types/node": "^22.15.18",
|
||||||
"@vitejs/plugin-vue": "^5.2.4",
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
"@wailsio/runtime": "latest",
|
"@wailsio/runtime": "latest",
|
||||||
"eslint": "^9.26.0",
|
"eslint": "^9.27.0",
|
||||||
"eslint-plugin-vue": "^10.1.0",
|
"eslint-plugin-vue": "^10.1.0",
|
||||||
"globals": "^16.1.0",
|
"globals": "^16.1.0",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.32.0",
|
"typescript-eslint": "^8.32.1",
|
||||||
"unplugin-vue-components": "^28.5.0",
|
"unplugin-vue-components": "^28.5.0",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.5",
|
||||||
"vue-eslint-parser": "^10.1.3",
|
"vue-eslint-parser": "^10.1.3",
|
||||||
|
@@ -105,7 +105,7 @@ const createEditor = async () => {
|
|||||||
// 应用初始字体大小
|
// 应用初始字体大小
|
||||||
editorStore.applyFontSize();
|
editorStore.applyFontSize();
|
||||||
|
|
||||||
// 立即更新统计信息,不等待用户交互
|
// 立即更新统计信息
|
||||||
updateStats(view, editorStore.updateDocumentStats);
|
updateStats(view, editorStore.updateDocumentStats);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@@ -44,9 +44,4 @@ export default {
|
|||||||
saveFailed: 'Failed to update save settings'
|
saveFailed: 'Failed to update save settings'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
settings: {
|
|
||||||
title: 'Settings',
|
|
||||||
general: 'General Settings',
|
|
||||||
comingSoon: 'More settings coming soon...'
|
|
||||||
}
|
|
||||||
};
|
};
|
@@ -46,7 +46,35 @@ export default {
|
|||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: '设置',
|
title: '设置',
|
||||||
general: '通用设置',
|
general: '常规',
|
||||||
comingSoon: '敬请期待更多设置选项...'
|
editing: '编辑器',
|
||||||
|
appearance: '外观',
|
||||||
|
keyBindings: '快捷键',
|
||||||
|
updates: '更新',
|
||||||
|
comingSoon: '即将推出...',
|
||||||
|
save: '保存',
|
||||||
|
reset: '重置',
|
||||||
|
globalHotkey: '全局键盘快捷键',
|
||||||
|
enableGlobalHotkey: '启用全局热键',
|
||||||
|
window: '窗口/应用程序',
|
||||||
|
showInSystemTray: '在系统托盘中显示',
|
||||||
|
alwaysOnTop: '窗口始终置顶',
|
||||||
|
bufferFiles: '缓冲文件路径',
|
||||||
|
useCustomLocation: '使用自定义位置存储缓冲文件',
|
||||||
|
selectDirectory: '选择目录',
|
||||||
|
fontSize: '字体大小',
|
||||||
|
fontSizeDescription: '编辑器字体大小',
|
||||||
|
tabSettings: 'Tab 设置',
|
||||||
|
tabSize: 'Tab 大小',
|
||||||
|
tabType: 'Tab 类型',
|
||||||
|
spaces: '空格',
|
||||||
|
tabs: '制表符',
|
||||||
|
enableTabIndent: '启用 Tab 缩进',
|
||||||
|
language: '界面语言',
|
||||||
|
restartRequired: '(需要重启)',
|
||||||
|
saveOptions: '保存选项',
|
||||||
|
autoSaveDelay: '自动保存延迟(毫秒)',
|
||||||
|
changeThreshold: '变更字符阈值',
|
||||||
|
minSaveInterval: '最小保存间隔(毫秒)'
|
||||||
}
|
}
|
||||||
};
|
};
|
@@ -1,6 +1,11 @@
|
|||||||
import {createRouter, createWebHistory, RouteRecordRaw} from 'vue-router';
|
import {createRouter, createWebHistory, RouteRecordRaw} from 'vue-router';
|
||||||
import Editor from '@/editor/Editor.vue';
|
import Editor from '@/editor/Editor.vue';
|
||||||
import SettingsPage from '@/settings/SettingsPage.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';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
@@ -11,7 +16,34 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
component: SettingsPage
|
component: Settings,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'general',
|
||||||
|
name: 'SettingsGeneral',
|
||||||
|
component: GeneralPage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'editing',
|
||||||
|
name: 'SettingsEditing',
|
||||||
|
component: EditingPage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'appearance',
|
||||||
|
name: 'SettingsAppearance',
|
||||||
|
component: AppearancePage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'key-bindings',
|
||||||
|
name: 'SettingsKeyBindings',
|
||||||
|
component: KeyBindingsPage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'updates',
|
||||||
|
name: 'SettingsUpdates',
|
||||||
|
component: UpdatesPage
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
215
frontend/src/settings/Settings.vue
Normal file
215
frontend/src/settings/Settings.vue
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useConfigStore } from '@/stores/configStore';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
// 导航配置
|
||||||
|
const navItems = [
|
||||||
|
{ id: 'general', icon: '⚙️', route: '/settings/general' },
|
||||||
|
{ id: 'editing', icon: '✏️', route: '/settings/editing' },
|
||||||
|
{ id: 'appearance', icon: '🎨', route: '/settings/appearance' },
|
||||||
|
{ id: 'keyBindings', icon: '⌨️', route: '/settings/key-bindings' },
|
||||||
|
{ id: 'updates', icon: '🔄', route: '/settings/updates' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 默认导航到常规设置
|
||||||
|
if (route.path === '/settings') {
|
||||||
|
router.replace('/settings/general');
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeNavItem = ref(route.path.split('/').pop() || 'general');
|
||||||
|
|
||||||
|
// 处理导航点击
|
||||||
|
const handleNavClick = (item: typeof navItems[0]) => {
|
||||||
|
activeNavItem.value = item.id;
|
||||||
|
router.push(item.route);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置设置
|
||||||
|
const resetSettings = async () => {
|
||||||
|
await configStore.resetToDefaults();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="settings-container">
|
||||||
|
<div class="settings-sidebar">
|
||||||
|
<div class="settings-header">
|
||||||
|
<h1>{{ t('settings.title') }}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="settings-nav">
|
||||||
|
<div
|
||||||
|
v-for="item in navItems"
|
||||||
|
:key="item.id"
|
||||||
|
class="nav-item"
|
||||||
|
:class="{ active: activeNavItem === item.id }"
|
||||||
|
@click="handleNavClick(item)"
|
||||||
|
>
|
||||||
|
<span class="nav-icon">{{ item.icon }}</span>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="settings-content">
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.settings-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
|
||||||
|
.settings-sidebar {
|
||||||
|
width: 200px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #333333;
|
||||||
|
border-right: 1px solid #444444;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.settings-header {
|
||||||
|
padding: 20px 16px;
|
||||||
|
border-bottom: 1px solid #444444;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-nav {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #555555;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #3c3c3c;
|
||||||
|
border-left-color: #4a9eff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-text {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-actions {
|
||||||
|
padding: 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;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-content {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
padding: 24px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #555555;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 2px solid #2a2a2a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义变量
|
||||||
|
:root {
|
||||||
|
--border-color: #444444;
|
||||||
|
--hover-color: #3a3a3a;
|
||||||
|
--active-bg: #3c3c3c;
|
||||||
|
--accent-color: #4a9eff;
|
||||||
|
--bg-primary: #2a2a2a;
|
||||||
|
--bg-secondary: #333333;
|
||||||
|
--text-primary: #e0e0e0;
|
||||||
|
--text-secondary: #a0a0a0;
|
||||||
|
--text-muted: #777777;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -1,68 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useConfigStore } from '@/stores/configStore';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const configStore = useConfigStore();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="settings-container">
|
|
||||||
<div class="settings-header">
|
|
||||||
<h1>{{ t('settings.title') }}</h1>
|
|
||||||
</div>
|
|
||||||
<div class="settings-content">
|
|
||||||
<!-- 设置内容将在此处添加 -->
|
|
||||||
<div class="settings-section">
|
|
||||||
<h2>{{ t('settings.general') }}</h2>
|
|
||||||
<div class="settings-placeholder">
|
|
||||||
{{ t('settings.comingSoon') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.settings-container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: var(--bg-primary);
|
|
||||||
color: var(--text-primary);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
.settings-header {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-content {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.settings-section {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 18px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-placeholder {
|
|
||||||
padding: 20px;
|
|
||||||
background-color: var(--bg-secondary);
|
|
||||||
border-radius: 6px;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
60
frontend/src/settings/components/SettingItem.vue
Normal file
60
frontend/src/settings/components/SettingItem.vue
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<div class="setting-title">{{ title }}</div>
|
||||||
|
<div v-if="description" class="setting-description">{{ description }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.setting-item {
|
||||||
|
display: flex;
|
||||||
|
padding: 14px 0;
|
||||||
|
border-bottom: 1px solid #444444;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-info {
|
||||||
|
flex: 1;
|
||||||
|
padding-right: 20px;
|
||||||
|
|
||||||
|
.setting-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-description {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #a0a0a0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-control {
|
||||||
|
width: 200px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
38
frontend/src/settings/components/SettingSection.vue
Normal file
38
frontend/src/settings/components/SettingSection.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
title: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="setting-section">
|
||||||
|
<h2 class="section-title">{{ title }}</h2>
|
||||||
|
<div class="section-content">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.setting-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
background-color: #333333;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background-color: #3d3d3d;
|
||||||
|
color: #ffffff;
|
||||||
|
border-bottom: 1px solid #444444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content {
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
53
frontend/src/settings/components/ToggleSwitch.vue
Normal file
53
frontend/src/settings/components/ToggleSwitch.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: boolean]
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
emit('update:modelValue', !props.modelValue);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="toggle-switch" :class="{ active: modelValue }" @click="toggle">
|
||||||
|
<div class="toggle-handle"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.toggle-switch {
|
||||||
|
width: 40px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #555555;
|
||||||
|
border-radius: 10px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #4a9eff;
|
||||||
|
|
||||||
|
.toggle-handle {
|
||||||
|
transform: translateX(20px);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
228
frontend/src/settings/pages/AppearancePage.vue
Normal file
228
frontend/src/settings/pages/AppearancePage.vue
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useConfigStore } from '@/stores/configStore';
|
||||||
|
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';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
|
// 语言选项
|
||||||
|
const languageOptions = [
|
||||||
|
{ value: 'zh-CN', label: t('languages.zh-CN') },
|
||||||
|
{ value: 'en-US', label: t('languages.en-US') },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 更新语言设置
|
||||||
|
const updateLanguage = (event: Event) => {
|
||||||
|
const select = event.target as HTMLSelectElement;
|
||||||
|
configStore.updateConfig('language', select.value as LanguageType);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 主题选择(未实际实现,仅界面展示)
|
||||||
|
const themeOptions = [
|
||||||
|
{ id: 'dark', name: '深色', color: '#2a2a2a' },
|
||||||
|
{ id: 'darker', name: '暗黑', color: '#1a1a1a' },
|
||||||
|
{ id: 'light', name: '浅色', color: '#f5f5f5' },
|
||||||
|
{ id: 'blue', name: '蓝调', color: '#1e3a5f' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const selectedTheme = ref('dark');
|
||||||
|
|
||||||
|
const selectTheme = (themeId: string) => {
|
||||||
|
selectedTheme.value = themeId;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="settings-page">
|
||||||
|
<SettingSection :title="t('settings.language')">
|
||||||
|
<SettingItem :title="t('settings.language')" :description="t('settings.restartRequired')">
|
||||||
|
<select class="select-input" :value="configStore.config.language" @change="updateLanguage">
|
||||||
|
<option v-for="option in languageOptions" :key="option.value" :value="option.value">
|
||||||
|
{{ option.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</SettingItem>
|
||||||
|
</SettingSection>
|
||||||
|
|
||||||
|
<SettingSection :title="t('settings.appearance')">
|
||||||
|
<div class="theme-selector">
|
||||||
|
<div class="selector-label">主题</div>
|
||||||
|
<div class="theme-options">
|
||||||
|
<div
|
||||||
|
v-for="theme in themeOptions"
|
||||||
|
:key="theme.id"
|
||||||
|
class="theme-option"
|
||||||
|
:class="{ active: selectedTheme === theme.id }"
|
||||||
|
@click="selectTheme(theme.id)"
|
||||||
|
>
|
||||||
|
<div class="color-preview" :style="{ backgroundColor: theme.color }"></div>
|
||||||
|
<div class="theme-name">{{ theme.name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="editor-preview">
|
||||||
|
<div class="preview-header">
|
||||||
|
<div class="preview-title">预览</div>
|
||||||
|
</div>
|
||||||
|
<div class="preview-content">
|
||||||
|
<div class="preview-line"><span class="line-number">1</span><span class="keyword">function</span> <span class="function">example</span>() {</div>
|
||||||
|
<div class="preview-line"><span class="line-number">2</span> <span class="keyword">const</span> greeting = <span class="string">"Hello, World!"</span>;</div>
|
||||||
|
<div class="preview-line"><span class="line-number">3</span> <span class="function">console.log</span>(greeting);</div>
|
||||||
|
<div class="preview-line"><span class="line-number">4</span>}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SettingSection>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.settings-page {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-input {
|
||||||
|
min-width: 150px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-size: 13px;
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e0e0e0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 8px center;
|
||||||
|
background-size: 16px;
|
||||||
|
padding-right: 30px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4a9eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-selector {
|
||||||
|
padding: 15px 16px;
|
||||||
|
|
||||||
|
.selector-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-options {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.theme-option {
|
||||||
|
width: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
.color-preview {
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-name {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
color: #c0c0c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .color-preview {
|
||||||
|
border-color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .color-preview {
|
||||||
|
border-color: #4a9eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .theme-name {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-preview {
|
||||||
|
margin: 20px 16px;
|
||||||
|
background-color: #252525;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
.preview-header {
|
||||||
|
padding: 10px 16px;
|
||||||
|
background-color: #353535;
|
||||||
|
border-bottom: 1px solid #444444;
|
||||||
|
|
||||||
|
.preview-title {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #b0b0b0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
padding: 12px 0;
|
||||||
|
font-family: 'Consolas', 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.preview-line {
|
||||||
|
padding: 3px 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-number {
|
||||||
|
color: #707070;
|
||||||
|
display: inline-block;
|
||||||
|
width: 25px;
|
||||||
|
margin-right: 15px;
|
||||||
|
text-align: right;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyword {
|
||||||
|
color: #569cd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function {
|
||||||
|
color: #dcdcaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.string {
|
||||||
|
color: #ce9178;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.coming-soon-placeholder {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #333333;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #a0a0a0;
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
239
frontend/src/settings/pages/EditingPage.vue
Normal file
239
frontend/src/settings/pages/EditingPage.vue
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useConfigStore } from '@/stores/configStore';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { ref, computed } 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';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
|
// 字体大小控制
|
||||||
|
const increaseFontSize = () => {
|
||||||
|
configStore.increaseFontSize();
|
||||||
|
};
|
||||||
|
|
||||||
|
const decreaseFontSize = () => {
|
||||||
|
configStore.decreaseFontSize();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tab类型切换
|
||||||
|
const tabTypeText = computed(() => {
|
||||||
|
return configStore.config.tabType === TabType.TabTypeSpaces
|
||||||
|
? t('settings.spaces')
|
||||||
|
: t('settings.tabs');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tab大小增减
|
||||||
|
const increaseTabSize = () => {
|
||||||
|
configStore.increaseTabSize();
|
||||||
|
};
|
||||||
|
|
||||||
|
const decreaseTabSize = () => {
|
||||||
|
configStore.decreaseTabSize();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="settings-page">
|
||||||
|
<SettingSection :title="t('settings.fontSize')">
|
||||||
|
<SettingItem
|
||||||
|
:title="t('settings.fontSize')"
|
||||||
|
:description="t('settings.fontSizeDescription')"
|
||||||
|
>
|
||||||
|
<div class="number-control">
|
||||||
|
<button @click="decreaseFontSize" class="control-button">-</button>
|
||||||
|
<span>{{ configStore.config.fontSize }}px</span>
|
||||||
|
<button @click="increaseFontSize" class="control-button">+</button>
|
||||||
|
</div>
|
||||||
|
</SettingItem>
|
||||||
|
<div class="font-size-preview" :style="{ fontSize: `${configStore.config.fontSize}px` }">
|
||||||
|
<div class="preview-label">预览</div>
|
||||||
|
<div class="preview-text">
|
||||||
|
<span>function example() {</span>
|
||||||
|
<span class="indent">console.log("Hello, World!");</span>
|
||||||
|
<span>}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SettingSection>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<span>{{ configStore.config.tabSize }}</span>
|
||||||
|
<button @click="increaseTabSize" class="control-button" :disabled="configStore.config.tabSize >= configStore.MAX_TAB_SIZE">+</button>
|
||||||
|
</div>
|
||||||
|
</SettingItem>
|
||||||
|
|
||||||
|
<SettingItem :title="t('settings.tabType')">
|
||||||
|
<button class="tab-type-toggle" @click="configStore.toggleTabType">
|
||||||
|
{{ tabTypeText }}
|
||||||
|
</button>
|
||||||
|
</SettingItem>
|
||||||
|
|
||||||
|
<SettingItem :title="t('settings.enableTabIndent')">
|
||||||
|
<ToggleSwitch
|
||||||
|
v-model="configStore.config.enableTabIndent"
|
||||||
|
@update:modelValue="configStore.toggleTabIndent"
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
</SettingSection>
|
||||||
|
|
||||||
|
<SettingSection :title="t('settings.saveOptions')">
|
||||||
|
<SettingItem :title="t('settings.autoSaveDelay')" :description="'单位:毫秒'">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="number-input"
|
||||||
|
disabled
|
||||||
|
:value="5000"
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
|
||||||
|
<SettingItem :title="t('settings.changeThreshold')" :description="'变更字符超过此阈值时触发保存'">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="number-input"
|
||||||
|
disabled
|
||||||
|
:value="500"
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
|
||||||
|
<SettingItem :title="t('settings.minSaveInterval')" :description="'单位:毫秒'">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="number-input"
|
||||||
|
disabled
|
||||||
|
:value="1000"
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
</SettingSection>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.settings-page {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.control-button {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-size-preview {
|
||||||
|
margin: 15px 0 5px 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #252525;
|
||||||
|
border: 1px solid #444444;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Consolas', 'Courier New', monospace;
|
||||||
|
|
||||||
|
.preview-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888888;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
span {
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #d0d0d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indent {
|
||||||
|
padding-left: 20px;
|
||||||
|
color: #4a9eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-type-toggle {
|
||||||
|
min-width: 100px;
|
||||||
|
padding: 8px 15px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.number-input {
|
||||||
|
width: 100px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
color: #a0a0a0;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4a9eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
213
frontend/src/settings/pages/GeneralPage.vue
Normal file
213
frontend/src/settings/pages/GeneralPage.vue
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useConfigStore } from '@/stores/configStore';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import SettingSection from '../components/SettingSection.vue';
|
||||||
|
import SettingItem from '../components/SettingItem.vue';
|
||||||
|
import ToggleSwitch from '../components/ToggleSwitch.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
|
// 选择的键盘修饰键
|
||||||
|
const selectedModifiers = ref({
|
||||||
|
ctrl: false,
|
||||||
|
shift: false,
|
||||||
|
alt: true,
|
||||||
|
altgr: false,
|
||||||
|
win: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// 选择的键
|
||||||
|
const selectedKey = ref('X');
|
||||||
|
|
||||||
|
// 可选键列表
|
||||||
|
const keyOptions = [
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||||
|
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12'
|
||||||
|
];
|
||||||
|
|
||||||
|
// 更新选择的键
|
||||||
|
const updateSelectedKey = (event: Event) => {
|
||||||
|
const select = event.target as HTMLSelectElement;
|
||||||
|
selectedKey.value = select.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换修饰键
|
||||||
|
const toggleModifier = (key: keyof typeof selectedModifiers.value) => {
|
||||||
|
selectedModifiers.value[key] = !selectedModifiers.value[key];
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="settings-page">
|
||||||
|
<SettingSection :title="t('settings.globalHotkey')">
|
||||||
|
<SettingItem :title="t('settings.enableGlobalHotkey')">
|
||||||
|
<ToggleSwitch v-model="configStore.config.alwaysOnTop" /> <!-- 此处使用alwaysOnTop作为示例 -->
|
||||||
|
</SettingItem>
|
||||||
|
|
||||||
|
<div class="hotkey-selector">
|
||||||
|
<div class="hotkey-modifiers">
|
||||||
|
<label class="modifier-label" :class="{ active: selectedModifiers.ctrl }">
|
||||||
|
<input type="checkbox" v-model="selectedModifiers.ctrl" class="hidden-checkbox">
|
||||||
|
<span class="modifier-key">Ctrl</span>
|
||||||
|
</label>
|
||||||
|
<label class="modifier-label" :class="{ active: selectedModifiers.shift }">
|
||||||
|
<input type="checkbox" v-model="selectedModifiers.shift" class="hidden-checkbox">
|
||||||
|
<span class="modifier-key">Shift</span>
|
||||||
|
</label>
|
||||||
|
<label class="modifier-label" :class="{ active: selectedModifiers.alt }">
|
||||||
|
<input type="checkbox" v-model="selectedModifiers.alt" class="hidden-checkbox">
|
||||||
|
<span class="modifier-key">Alt</span>
|
||||||
|
</label>
|
||||||
|
<label class="modifier-label" :class="{ active: selectedModifiers.altgr }">
|
||||||
|
<input type="checkbox" v-model="selectedModifiers.altgr" class="hidden-checkbox">
|
||||||
|
<span class="modifier-key">AltGr</span>
|
||||||
|
</label>
|
||||||
|
<label class="modifier-label" :class="{ active: selectedModifiers.win }">
|
||||||
|
<input type="checkbox" v-model="selectedModifiers.win" class="hidden-checkbox">
|
||||||
|
<span class="modifier-key">Win</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<select class="key-select" v-model="selectedKey">
|
||||||
|
<option v-for="key in keyOptions" :key="key" :value="key">{{ key }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</SettingSection>
|
||||||
|
|
||||||
|
<SettingSection :title="t('settings.window')">
|
||||||
|
<SettingItem :title="t('settings.showInSystemTray')">
|
||||||
|
<ToggleSwitch v-model="configStore.config.alwaysOnTop" /> <!-- 需要后端实现 -->
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem :title="t('settings.alwaysOnTop')">
|
||||||
|
<ToggleSwitch v-model="configStore.config.alwaysOnTop" @update:modelValue="configStore.toggleAlwaysOnTop" />
|
||||||
|
</SettingItem>
|
||||||
|
</SettingSection>
|
||||||
|
|
||||||
|
<SettingSection :title="t('settings.bufferFiles')">
|
||||||
|
<SettingItem :title="t('settings.useCustomLocation')">
|
||||||
|
<ToggleSwitch v-model="configStore.config.alwaysOnTop" /> <!-- 需要后端实现 -->
|
||||||
|
</SettingItem>
|
||||||
|
<div class="directory-selector">
|
||||||
|
<div class="path-display">{{ configStore.config.alwaysOnTop ? 'C:/Custom/Path' : 'Default Location' }}</div>
|
||||||
|
<button class="select-button">{{ t('settings.selectDirectory') }}</button>
|
||||||
|
</div>
|
||||||
|
</SettingSection>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.settings-page {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotkey-selector {
|
||||||
|
padding: 15px 0 5px 20px;
|
||||||
|
|
||||||
|
.hotkey-modifiers {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.modifier-label {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.hidden-checkbox {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modifier-key {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background-color: #444444;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #b0b0b0;
|
||||||
|
font-size: 13px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .modifier-key {
|
||||||
|
background-color: #2c5a9e;
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: #3a6db1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-select {
|
||||||
|
min-width: 80px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-size: 13px;
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e0e0e0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 8px center;
|
||||||
|
background-size: 16px;
|
||||||
|
padding-right: 30px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4a9eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.directory-selector {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-left: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.path-display {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #b0b0b0;
|
||||||
|
font-size: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
219
frontend/src/settings/pages/KeyBindingsPage.vue
Normal file
219
frontend/src/settings/pages/KeyBindingsPage.vue
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import SettingSection from '../components/SettingSection.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
interface KeyBinding {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
keys: string[];
|
||||||
|
isEditing: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 示例快捷键列表(仅用于界面展示)
|
||||||
|
const keyBindings = ref<KeyBinding[]>([
|
||||||
|
{ id: 'save', name: '保存文档', keys: ['Ctrl', 'S'], isEditing: false },
|
||||||
|
{ id: 'new', name: '新建文档', keys: ['Ctrl', 'N'], isEditing: false },
|
||||||
|
{ id: 'open', name: '打开文档', keys: ['Ctrl', 'O'], isEditing: false },
|
||||||
|
{ id: 'find', name: '查找', keys: ['Ctrl', 'F'], isEditing: false },
|
||||||
|
{ id: 'replace', name: '替换', keys: ['Ctrl', 'H'], isEditing: false },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 切换编辑状态
|
||||||
|
const toggleEdit = (binding: KeyBinding) => {
|
||||||
|
// 先关闭其他所有编辑中的项
|
||||||
|
keyBindings.value.forEach(item => {
|
||||||
|
if (item.id !== binding.id) {
|
||||||
|
item.isEditing = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 切换当前项
|
||||||
|
binding.isEditing = !binding.isEditing;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑模式下按键事件处理
|
||||||
|
const handleKeyDown = (event: KeyboardEvent, binding: KeyBinding) => {
|
||||||
|
if (!binding.isEditing) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const newKeys: string[] = [];
|
||||||
|
if (event.ctrlKey) newKeys.push('Ctrl');
|
||||||
|
if (event.shiftKey) newKeys.push('Shift');
|
||||||
|
if (event.altKey) newKeys.push('Alt');
|
||||||
|
|
||||||
|
// 获取按键
|
||||||
|
let keyName = event.key;
|
||||||
|
if (keyName === ' ') keyName = 'Space';
|
||||||
|
if (keyName.length === 1) keyName = keyName.toUpperCase();
|
||||||
|
|
||||||
|
// 如果有修饰键,就添加主键
|
||||||
|
if (event.ctrlKey || event.shiftKey || event.altKey) {
|
||||||
|
if (!['Control', 'Shift', 'Alt'].includes(keyName)) {
|
||||||
|
newKeys.push(keyName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 没有修饰键,直接使用主键
|
||||||
|
newKeys.push(keyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 唯一按键,不增加空字段
|
||||||
|
if (newKeys.length > 0) {
|
||||||
|
binding.keys = [...new Set(newKeys)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成编辑
|
||||||
|
binding.isEditing = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="settings-page">
|
||||||
|
<SettingSection :title="t('settings.keyBindings')">
|
||||||
|
<div class="key-bindings-container">
|
||||||
|
<div class="key-bindings-header">
|
||||||
|
<div class="command-col">命令</div>
|
||||||
|
<div class="keybinding-col">快捷键</div>
|
||||||
|
<div class="action-col">操作</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="binding in keyBindings"
|
||||||
|
:key="binding.id"
|
||||||
|
class="key-binding-row"
|
||||||
|
:class="{ 'is-editing': binding.isEditing }"
|
||||||
|
@keydown="(e) => handleKeyDown(e, binding)"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div class="command-col">{{ binding.name }}</div>
|
||||||
|
<div class="keybinding-col" :class="{ 'is-editing': binding.isEditing }">
|
||||||
|
<template v-if="binding.isEditing">
|
||||||
|
按下快捷键...
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span
|
||||||
|
v-for="(key, index) in binding.keys"
|
||||||
|
:key="index"
|
||||||
|
class="key-badge"
|
||||||
|
>
|
||||||
|
{{ key }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="action-col">
|
||||||
|
<button
|
||||||
|
class="edit-button"
|
||||||
|
@click="toggleEdit(binding)"
|
||||||
|
>
|
||||||
|
{{ binding.isEditing ? '取消' : '编辑' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SettingSection>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.settings-page {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-bindings-container {
|
||||||
|
padding: 10px 16px;
|
||||||
|
|
||||||
|
.key-bindings-header {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 0 10px 0;
|
||||||
|
border-bottom: 1px solid #444444;
|
||||||
|
color: #a0a0a0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-binding-row {
|
||||||
|
display: flex;
|
||||||
|
padding: 14px 0;
|
||||||
|
border-bottom: 1px solid #3a3a3a;
|
||||||
|
align-items: center;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-editing {
|
||||||
|
background-color: rgba(74, 158, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-col {
|
||||||
|
flex: 1;
|
||||||
|
padding-right: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keybinding-col {
|
||||||
|
width: 200px;
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
|
&.is-editing {
|
||||||
|
font-style: italic;
|
||||||
|
color: #a0a0a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-badge {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-col {
|
||||||
|
width: 80px;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.edit-button {
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.coming-soon-placeholder {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #333333;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #a0a0a0;
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
223
frontend/src/settings/pages/UpdatesPage.vue
Normal file
223
frontend/src/settings/pages/UpdatesPage.vue
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import SettingSection from '../components/SettingSection.vue';
|
||||||
|
import SettingItem from '../components/SettingItem.vue';
|
||||||
|
import ToggleSwitch from '../components/ToggleSwitch.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
// 模拟版本数据
|
||||||
|
const currentVersion = ref('1.0.0');
|
||||||
|
const isCheckingForUpdates = ref(false);
|
||||||
|
const updateAvailable = ref(false);
|
||||||
|
const latestVersion = ref('1.1.0');
|
||||||
|
const updateNotes = ref([
|
||||||
|
'优化编辑器性能',
|
||||||
|
'新增自动保存功能',
|
||||||
|
'修复多个界面显示问题',
|
||||||
|
'添加更多编辑器主题'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 自动检查更新选项
|
||||||
|
const autoCheckUpdates = ref(true);
|
||||||
|
|
||||||
|
// 模拟检查更新
|
||||||
|
const checkForUpdates = () => {
|
||||||
|
isCheckingForUpdates.value = true;
|
||||||
|
|
||||||
|
// 模拟网络请求延迟
|
||||||
|
setTimeout(() => {
|
||||||
|
isCheckingForUpdates.value = false;
|
||||||
|
updateAvailable.value = true;
|
||||||
|
}, 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模拟下载更新
|
||||||
|
const downloadUpdate = () => {
|
||||||
|
// 在实际应用中这里会调用后端API下载更新
|
||||||
|
alert('开始下载更新...');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="settings-page">
|
||||||
|
<SettingSection :title="t('settings.updates')">
|
||||||
|
<div class="update-info">
|
||||||
|
<div class="version-info">
|
||||||
|
<div class="current-version">
|
||||||
|
<span class="label">当前版本:</span>
|
||||||
|
<span class="version">{{ currentVersion }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="check-button"
|
||||||
|
@click="checkForUpdates"
|
||||||
|
:disabled="isCheckingForUpdates"
|
||||||
|
>
|
||||||
|
<span v-if="isCheckingForUpdates" class="loading-spinner"></span>
|
||||||
|
{{ isCheckingForUpdates ? '检查中...' : '检查更新' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="updateAvailable" class="update-available">
|
||||||
|
<div class="update-header">
|
||||||
|
<div class="update-title">发现新版本: {{ latestVersion }}</div>
|
||||||
|
<button class="download-button" @click="downloadUpdate">
|
||||||
|
下载更新
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="update-notes">
|
||||||
|
<div class="notes-title">更新内容:</div>
|
||||||
|
<ul class="notes-list">
|
||||||
|
<li v-for="(note, index) in updateNotes" :key="index">
|
||||||
|
{{ note }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SettingItem title="自动检查更新" description="启动应用时自动检查更新">
|
||||||
|
<ToggleSwitch v-model="autoCheckUpdates" />
|
||||||
|
</SettingItem>
|
||||||
|
</SettingSection>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.settings-page {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-info {
|
||||||
|
padding: 15px 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.version-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.current-version {
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #a0a0a0;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: #444444;
|
||||||
|
border-color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top-color: #e0e0e0;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-available {
|
||||||
|
background-color: #2c3847;
|
||||||
|
border: 1px solid #3a4a5c;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.update-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.update-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #4a9eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #2c5a9e;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #3867a9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-notes {
|
||||||
|
.notes-title {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #b0b0b0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes-list {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #d0d0d0;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
60
internal/events/tray_events.go
Normal file
60
internal/events/tray_events.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package events
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
wailsevents "github.com/wailsapp/wails/v3/pkg/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterTrayEvents 注册与系统托盘相关的所有事件
|
||||||
|
func RegisterTrayEvents(app *application.App, systray *application.SystemTray, mainWindow *application.WebviewWindow, settingsWindow *application.WebviewWindow) {
|
||||||
|
// 不附加窗口到系统托盘,避免失去焦点自动缩小
|
||||||
|
// systray.AttachWindow(mainWindow)
|
||||||
|
|
||||||
|
// 设置窗口防抖时间
|
||||||
|
systray.WindowDebounce(200 * time.Millisecond)
|
||||||
|
|
||||||
|
// 设置点击托盘图标显示主窗口
|
||||||
|
systray.OnClick(func() {
|
||||||
|
mainWindow.Show()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 使用关闭前的事件处理
|
||||||
|
mainWindow.RegisterHook(wailsevents.Common.WindowClosing, func(event *application.WindowEvent) {
|
||||||
|
// 取消默认关闭行为
|
||||||
|
event.Cancel()
|
||||||
|
// 隐藏窗口
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
app.Quit()
|
||||||
|
})
|
||||||
|
}
|
@@ -2,10 +2,8 @@ package systray
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"github.com/wailsapp/wails/v3/pkg/events"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
"voidraft/internal/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetupSystemTray 设置系统托盘及其功能
|
// SetupSystemTray 设置系统托盘及其功能
|
||||||
@@ -22,48 +20,12 @@ func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow
|
|||||||
|
|
||||||
// 创建托盘菜单
|
// 创建托盘菜单
|
||||||
menu := app.NewMenu()
|
menu := app.NewMenu()
|
||||||
menu.Add("主窗口").OnClick(func(data *application.Context) {
|
|
||||||
mainWindow.Show()
|
// 注册托盘菜单事件
|
||||||
})
|
events.RegisterTrayMenuEvents(app, menu, mainWindow, settingsWindow)
|
||||||
menu.Add("设置").OnClick(func(data *application.Context) {
|
|
||||||
settingsWindow.Show()
|
|
||||||
})
|
|
||||||
menu.AddSeparator()
|
|
||||||
menu.Add("退出").OnClick(func(data *application.Context) {
|
|
||||||
app.Quit()
|
|
||||||
})
|
|
||||||
|
|
||||||
systray.SetMenu(menu)
|
systray.SetMenu(menu)
|
||||||
|
|
||||||
// 设置点击托盘图标显示主窗口
|
// 注册托盘相关事件
|
||||||
systray.OnClick(func() {
|
events.RegisterTrayEvents(app, systray, mainWindow, settingsWindow)
|
||||||
mainWindow.Show()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 不再附加窗口到系统托盘,避免失去焦点自动缩小
|
|
||||||
// systray.AttachWindow(mainWindow)
|
|
||||||
|
|
||||||
// 设置窗口防抖时间
|
|
||||||
systray.WindowDebounce(200 * time.Millisecond)
|
|
||||||
|
|
||||||
// 使用关闭前的事件处理
|
|
||||||
mainWindow.RegisterHook(events.Common.WindowClosing, func(event *application.WindowEvent) {
|
|
||||||
// 取消默认关闭行为
|
|
||||||
event.Cancel()
|
|
||||||
// 隐藏窗口
|
|
||||||
mainWindow.Hide()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 设置窗口关闭事件处理
|
|
||||||
settingsWindow.RegisterHook(events.Common.WindowClosing, func(event *application.WindowEvent) {
|
|
||||||
// 取消默认关闭行为
|
|
||||||
event.Cancel()
|
|
||||||
// 隐藏窗口
|
|
||||||
settingsWindow.Hide()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 注册事件监听器,用于处理前端发送的显示设置窗口事件
|
|
||||||
app.OnEvent("show_settings_window", func(event *application.CustomEvent) {
|
|
||||||
settingsWindow.Show()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
13
main.go
13
main.go
@@ -62,11 +62,14 @@ func main() {
|
|||||||
})
|
})
|
||||||
mainWindow.Center()
|
mainWindow.Center()
|
||||||
settingsWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
settingsWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||||
Title: "voidraft设置",
|
Title: "voidraft设置",
|
||||||
Width: 500,
|
Width: 700,
|
||||||
Height: 600,
|
Height: 800,
|
||||||
Hidden: true, // 初始时隐藏设置窗口
|
Hidden: true,
|
||||||
AlwaysOnTop: true,
|
AlwaysOnTop: true,
|
||||||
|
DisableResize: true,
|
||||||
|
MinimiseButtonState: application.ButtonHidden,
|
||||||
|
MaximiseButtonState: application.ButtonHidden,
|
||||||
Mac: application.MacWindow{
|
Mac: application.MacWindow{
|
||||||
Backdrop: application.MacBackdropTranslucent,
|
Backdrop: application.MacBackdropTranslucent,
|
||||||
TitleBar: application.MacTitleBarHiddenInset,
|
TitleBar: application.MacTitleBarHiddenInset,
|
||||||
|
Reference in New Issue
Block a user