diff --git a/frontend/bindings/voidraft/internal/models/models.ts b/frontend/bindings/voidraft/internal/models/models.ts index 9e59d82..64ff228 100644 --- a/frontend/bindings/voidraft/internal/models/models.ts +++ b/frontend/bindings/voidraft/internal/models/models.ts @@ -1001,11 +1001,6 @@ export class UpdatesConfig { */ "autoUpdate": boolean; - /** - * 是否启用测试版 - */ - "betaChannel": boolean; - /** Creates a new UpdatesConfig instance. */ constructor($$source: Partial = {}) { if (!("version" in $$source)) { @@ -1014,9 +1009,6 @@ export class UpdatesConfig { if (!("autoUpdate" in $$source)) { this["autoUpdate"] = false; } - if (!("betaChannel" in $$source)) { - this["betaChannel"] = false; - } Object.assign(this, $$source); } diff --git a/frontend/bindings/voidraft/internal/services/index.ts b/frontend/bindings/voidraft/internal/services/index.ts index b9eb333..980bd62 100644 --- a/frontend/bindings/voidraft/internal/services/index.ts +++ b/frontend/bindings/voidraft/internal/services/index.ts @@ -10,6 +10,7 @@ import * as MigrationService from "./migrationservice.js"; import * as StartupService from "./startupservice.js"; import * as SystemService from "./systemservice.js"; import * as TrayService from "./trayservice.js"; +import * as UpdateService from "./updateservice.js"; export { ConfigService, DialogService, @@ -19,7 +20,8 @@ export { MigrationService, StartupService, SystemService, - TrayService + TrayService, + UpdateService }; export * from "./models.js"; diff --git a/frontend/bindings/voidraft/internal/services/models.ts b/frontend/bindings/voidraft/internal/services/models.ts index f2b18ab..09f61c4 100644 --- a/frontend/bindings/voidraft/internal/services/models.ts +++ b/frontend/bindings/voidraft/internal/services/models.ts @@ -114,3 +114,70 @@ export enum MigrationStatus { MigrationStatusCompleted = "completed", MigrationStatusFailed = "failed", }; + +/** + * UpdateCheckResult 更新检查结果 + */ +export class UpdateCheckResult { + /** + * 是否有更新 + */ + "hasUpdate": boolean; + + /** + * 当前版本 + */ + "currentVer": string; + + /** + * 最新版本 + */ + "latestVer": string; + + /** + * 发布说明 + */ + "releaseNotes": string; + + /** + * 发布页面URL + */ + "releaseURL": string; + + /** + * 错误信息 + */ + "error": string; + + /** Creates a new UpdateCheckResult instance. */ + constructor($$source: Partial = {}) { + if (!("hasUpdate" in $$source)) { + this["hasUpdate"] = false; + } + if (!("currentVer" in $$source)) { + this["currentVer"] = ""; + } + if (!("latestVer" in $$source)) { + this["latestVer"] = ""; + } + if (!("releaseNotes" in $$source)) { + this["releaseNotes"] = ""; + } + if (!("releaseURL" in $$source)) { + this["releaseURL"] = ""; + } + if (!("error" in $$source)) { + this["error"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new UpdateCheckResult instance from a string or object. + */ + static createFrom($$source: any = {}): UpdateCheckResult { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new UpdateCheckResult($$parsedSource as Partial); + } +} diff --git a/frontend/bindings/voidraft/internal/services/updateservice.ts b/frontend/bindings/voidraft/internal/services/updateservice.ts new file mode 100644 index 0000000..5244a58 --- /dev/null +++ b/frontend/bindings/voidraft/internal/services/updateservice.ts @@ -0,0 +1,30 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * UpdateService 更新服务 + * @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"; + +/** + * CheckForUpdates 检查更新 + */ +export function CheckForUpdates(): Promise<$models.UpdateCheckResult> & { cancel(): void } { + let $resultPromise = $Call.ByID(3024322786) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType0($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +// Private type creation functions +const $$createType0 = $models.UpdateCheckResult.createFrom; diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 67c8b75..fb05e86 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -4,12 +4,14 @@ import { useConfigStore } from '@/stores/configStore'; import { useSystemStore } from '@/stores/systemStore'; import { useKeybindingStore } from '@/stores/keybindingStore'; import { useThemeStore } from '@/stores/themeStore'; +import { useUpdateStore } from '@/stores/updateStore'; import WindowTitleBar from '@/components/titlebar/WindowTitleBar.vue'; const configStore = useConfigStore(); const systemStore = useSystemStore(); const keybindingStore = useKeybindingStore(); const themeStore = useThemeStore(); +const updateStore = useUpdateStore(); // 应用启动时加载配置和初始化系统信息 onMounted(async () => { @@ -23,6 +25,9 @@ onMounted(async () => { // 初始化语言和主题 await configStore.initializeLanguage(); themeStore.initializeTheme(); + + // 启动时检查更新 + await updateStore.checkOnStartup(); }); diff --git a/frontend/src/components/toolbar/Toolbar.vue b/frontend/src/components/toolbar/Toolbar.vue index 77c3543..13f679d 100644 --- a/frontend/src/components/toolbar/Toolbar.vue +++ b/frontend/src/components/toolbar/Toolbar.vue @@ -3,6 +3,7 @@ import {useI18n} from 'vue-i18n'; import {onMounted, onUnmounted, ref, watch} from 'vue'; import {useConfigStore} from '@/stores/configStore'; import {useEditorStore} from '@/stores/editorStore'; +import {useUpdateStore} from '@/stores/updateStore'; import * as runtime from '@wailsio/runtime'; import {useRouter} from 'vue-router'; import BlockLanguageSelector from './BlockLanguageSelector.vue'; @@ -11,6 +12,7 @@ import {getLanguage} from '@/views/editor/extensions/codeblock/lang-parser/langu const editorStore = useEditorStore(); const configStore = useConfigStore(); +const updateStore = useUpdateStore(); const {t} = useI18n(); const router = useRouter(); @@ -176,6 +178,21 @@ watch(isLoaded, async (newLoaded) => { + +
+ + + + + +
+
{ } + /* 更新提示按钮样式 */ + .update-button { + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + padding: 2px; + border-radius: 3px; + background-color: rgba(76, 175, 80, 0.1); + transition: all 0.2s ease; + animation: pulse 2s infinite; + + &:hover { + background-color: rgba(76, 175, 80, 0.2); + transform: scale(1.05); + } + + svg { + stroke: #4caf50; + } + + @keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.7; + } + } + } + /* 窗口置顶图标按钮样式 */ .pin-button { cursor: pointer; diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 58dbd99..9db271e 100644 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -135,6 +135,19 @@ export default { language: 'Interface Language', systemTheme: 'System Theme', saveOptions: 'Save Options', - autoSaveDelay: 'Auto Save Delay (ms)' + autoSaveDelay: 'Auto Save Delay (ms)', + updateSettings: 'Update Settings', + autoCheckUpdates: 'Auto Check Updates', + autoCheckUpdatesDescription: 'Automatically check for updates on startup', + manualCheck: 'Manual Check', + currentVersion: 'Current Version', + checkForUpdates: 'Check for Updates', + checking: 'Checking...', + checkFailed: 'Check Failed', + newVersionAvailable: 'New Version Available', + upToDate: 'You are using the latest version', + viewUpdate: 'View Update', + releaseNotes: 'Release Notes', + networkError: 'Network connection error, please check your network settings' } }; \ No newline at end of file diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index 9d109da..d102e8c 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -135,6 +135,19 @@ export default { language: '界面语言', systemTheme: '系统主题', saveOptions: '保存选项', - autoSaveDelay: '自动保存延迟(毫秒)' + autoSaveDelay: '自动保存延迟(毫秒)', + updateSettings: '更新设置', + autoCheckUpdates: '自动检查更新', + autoCheckUpdatesDescription: '启动应用时自动检查更新', + manualCheck: '手动检查', + currentVersion: '当前版本', + checkForUpdates: '检查更新', + checking: '检查中...', + checkFailed: '检查失败', + newVersionAvailable: '发现新版本', + upToDate: '您正在使用最新版本', + viewUpdate: '查看更新', + releaseNotes: '更新内容', + networkError: '网络连接错误,请检查网络设置' } }; \ No newline at end of file diff --git a/frontend/src/stores/configStore.ts b/frontend/src/stores/configStore.ts index 28dcd60..67159e3 100644 --- a/frontend/src/stores/configStore.ts +++ b/frontend/src/stores/configStore.ts @@ -9,6 +9,7 @@ import { LanguageType, SystemThemeType, TabType, + UpdatesConfig, } from '@/../bindings/voidraft/internal/models/models'; import {useI18n} from 'vue-i18n'; import {ConfigUtils} from '@/utils/configUtils'; @@ -42,6 +43,10 @@ type AppearanceConfigKeyMap = { readonly [K in keyof AppearanceConfig]: string; }; +type UpdatesConfigKeyMap = { + readonly [K in keyof UpdatesConfig]: string; +}; + type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight'; // 配置键映射 @@ -70,6 +75,11 @@ const APPEARANCE_CONFIG_KEY_MAP: AppearanceConfigKeyMap = { systemTheme: 'appearance.systemTheme' } as const; +const UPDATES_CONFIG_KEY_MAP: UpdatesConfigKeyMap = { + version: 'updates.version', + autoUpdate: 'updates.autoUpdate' +} as const; + // 配置限制 const CONFIG_LIMITS = { fontSize: {min: 12, max: 28, default: 13}, @@ -145,7 +155,6 @@ const DEFAULT_CONFIG: AppConfig = { updates: { version: "1.0.0", autoUpdate: true, - betaChannel: false }, metadata: { version: '1.0.0', @@ -216,6 +225,21 @@ export const useConfigStore = defineStore('config', () => { state.config.appearance[key] = value; }; + const updateUpdatesConfig = async (key: K, value: UpdatesConfig[K]): Promise => { + // 确保配置已加载 + if (!state.configLoaded && !state.isLoading) { + await initConfig(); + } + + const backendKey = UPDATES_CONFIG_KEY_MAP[key]; + if (!backendKey) { + throw new Error(`No backend key mapping found for updates.${key.toString()}`); + } + + await ConfigService.Set(backendKey, value); + state.config.updates[key] = value; + }; + // 加载配置 const initConfig = async (): Promise => { if (state.isLoading) return; @@ -411,6 +435,9 @@ export const useConfigStore = defineStore('config', () => { await updateGeneralConfig('startAtLogin', value); // 再调用系统设置API await StartupService.SetEnabled(value); - } + }, + + // 更新配置相关方法 + setAutoUpdate: async (value: boolean) => await updateUpdatesConfig('autoUpdate', value) }; }); \ No newline at end of file diff --git a/frontend/src/stores/updateStore.ts b/frontend/src/stores/updateStore.ts new file mode 100644 index 0000000..208df12 --- /dev/null +++ b/frontend/src/stores/updateStore.ts @@ -0,0 +1,81 @@ +import {defineStore} from 'pinia' +import {computed, ref} from 'vue' +import {CheckForUpdates} from '@/../bindings/voidraft/internal/services/updateservice' +import {UpdateCheckResult} from '@/../bindings/voidraft/internal/services/models' +import {useConfigStore} from './configStore' + +export const useUpdateStore = defineStore('update', () => { + // 状态 + const isChecking = ref(false) + const updateResult = ref(null) + const hasCheckedOnStartup = ref(false) + + // 计算属性 + const hasUpdate = computed(() => updateResult.value?.hasUpdate || false) + const errorMessage = computed(() => updateResult.value?.error || '') + + // 检查更新 + const checkForUpdates = async (): Promise => { + if (isChecking.value) return false + + isChecking.value = true + try { + const result = await CheckForUpdates() + updateResult.value = result + return !result.error + } catch (error) { + updateResult.value = new UpdateCheckResult({ + hasUpdate: false, + currentVer: '1.0.0', + latestVer: '', + releaseNotes: '', + releaseURL: '', + error: 'Network error' + }) + return false + } finally { + isChecking.value = false + } + } + + // 启动时检查更新 + const checkOnStartup = async () => { + if (hasCheckedOnStartup.value) return + const configStore = useConfigStore() + + if (configStore.config.updates.autoUpdate) { + await checkForUpdates() + } + hasCheckedOnStartup.value = true + } + + // 打开发布页面 + const openReleaseURL = () => { + if (updateResult.value?.releaseURL) { + window.open(updateResult.value.releaseURL, '_blank') + } + } + + // 重置状态 + const reset = () => { + updateResult.value = null + isChecking.value = false + } + + return { + // 状态 + isChecking, + updateResult, + hasCheckedOnStartup, + + // 计算属性 + hasUpdate, + errorMessage, + + // 方法 + checkForUpdates, + checkOnStartup, + openReleaseURL, + reset + } +}) \ No newline at end of file diff --git a/frontend/src/views/settings/pages/KeyBindingsPage.vue b/frontend/src/views/settings/pages/KeyBindingsPage.vue index 163044f..ef4d5a4 100644 --- a/frontend/src/views/settings/pages/KeyBindingsPage.vue +++ b/frontend/src/views/settings/pages/KeyBindingsPage.vue @@ -186,11 +186,6 @@ const parseKeyBinding = (keyStr: string, command?: string): string[] => { return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase(); }).filter(part => part.length > 0); }; - -// 组件挂载时加载快捷键数据 -onMounted(async () => { - await keybindingStore.loadKeyBindings(); -});