diff --git a/src/renderer/components.d.ts b/src/renderer/components.d.ts index 7fe6331..b9eb126 100644 --- a/src/renderer/components.d.ts +++ b/src/renderer/components.d.ts @@ -28,6 +28,7 @@ declare module 'vue' { Sidebar: typeof import('./src/components/Sidebar.vue')['default'] Toast: typeof import('primevue/toast')['default'] ToggleSwitch: typeof import('primevue/toggleswitch')['default'] + Update: typeof import('./src/components/Update.vue')['default'] UpdateNotification: typeof import('./src/components/UpdateNotification.vue')['default'] } export interface GlobalDirectives { diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue index 98e5fca..4d679f0 100644 --- a/src/renderer/src/App.vue +++ b/src/renderer/src/App.vue @@ -1,5 +1,10 @@ - + + @@ -10,7 +15,7 @@ import { mittbus } from './ipc' const toast = useToast() -mittbus.on('toast:add', payload => toast.add(payload)) +mittbus.on('toast:add', payload => toast.add({ ...{ group: 'tr' }, ...payload })) onUnmounted(() => { toast.removeAllGroups() diff --git a/src/renderer/src/components/About.vue b/src/renderer/src/components/About.vue index fa7f038..f83dad0 100644 --- a/src/renderer/src/components/About.vue +++ b/src/renderer/src/components/About.vue @@ -19,8 +19,6 @@ const handleCheckUpdate = async () => { const result = await checkForUpdate() if (result && result.updateInfo) { logger.info('Update available:', result.updateInfo) - // Here you might want to show a dialog or notification - // For now, we just log it as per request "add function" } else { logger.info('No update available') } diff --git a/src/renderer/src/components/Layout.vue b/src/renderer/src/components/Layout.vue index d5bb6d0..7725bdb 100644 --- a/src/renderer/src/components/Layout.vue +++ b/src/renderer/src/components/Layout.vue @@ -2,7 +2,7 @@ import logger from 'electron-log/renderer' import { onUnmounted } from 'vue' import Sidebar from './Sidebar.vue' -import UpdateNotification from './UpdateNotification.vue' +import UpdateNotification from './Update.vue' logger.debug('Layout created') onUnmounted(() => { logger.debug('Layout unmounted') diff --git a/src/renderer/src/components/Sidebar.vue b/src/renderer/src/components/Sidebar.vue index c9cb096..b71ee4f 100644 --- a/src/renderer/src/components/Sidebar.vue +++ b/src/renderer/src/components/Sidebar.vue @@ -1,7 +1,7 @@ - + - + +import { useUpdateStore } from '@renderer/store/update' +import { storeToRefs } from 'pinia' +import { onMounted, onUnmounted } from 'vue' + +const updateStore = useUpdateStore() +const { updateAvailable, updateDownloaded, downloading, downloadProgress } = storeToRefs(updateStore) +const { startDownload, quitAndInstall, init, destroy } = updateStore + +onMounted(() => { + init() +}) + +onUnmounted(() => { + destroy() +}) + + + + + + + + + 下载中 + + + + + 重启安装 + + + + 新版本可用 + + + + + + + diff --git a/src/renderer/src/components/UpdateNotification.vue b/src/renderer/src/components/UpdateNotification.vue deleted file mode 100644 index cb1f72f..0000000 --- a/src/renderer/src/components/UpdateNotification.vue +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - {{ updateDownloaded ? '更新已就绪' : '发现新版本' }} - - - - - 更新 - - - - - - - - {{ downloadProgress }}% - - - - - 重启安装 - - - - - - - diff --git a/src/renderer/src/store/preference.ts b/src/renderer/src/store/preference.ts index e4e1378..e6c0959 100644 --- a/src/renderer/src/store/preference.ts +++ b/src/renderer/src/store/preference.ts @@ -5,7 +5,7 @@ import { reactive, toRaw, watch } from 'vue' interface UserPreference extends UserStore {} -export const usePreferenceStore = defineStore('setting', () => { +export const usePreferenceStore = defineStore('preference', () => { const preference = reactive({ 'convert-config': { cachePath: '', diff --git a/src/renderer/src/store/update.ts b/src/renderer/src/store/update.ts new file mode 100644 index 0000000..ba5d455 --- /dev/null +++ b/src/renderer/src/store/update.ts @@ -0,0 +1,147 @@ +import { + checkForUpdate, + downloadUpdate as downloadUpdateApi, + quitAndInstall as quitAndInstallApi, + subscribeUpdateAvailable, + subscribeUpdateDownloaded, + subscribeUpdateError, + subscribeUpdateNotAvailable, + subscribeUpdateProgress +} from '@renderer/api' +import { mittbus } from '@renderer/ipc' +import logger from 'electron-log/renderer' +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export const useUpdateStore = defineStore('update', () => { + const updateAvailable = ref(false) + const updateDownloaded = ref(false) + const downloading = ref(false) + const downloadProgress = ref(0) + const updateVersion = ref('') + + const unsubscribes: (() => void)[] = [] + + const registerSubscribe = (fn: () => void): void => { + unsubscribes.push(fn) + } + + const unregisterSubscribes = (): void => { + unsubscribes.forEach(fn => fn && fn()) + unsubscribes.length = 0 + } + + const checkUpdates = async () => { + try { + await checkForUpdate() + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + mittbus.emit('toast:add', { + group: 'br', + severity: 'error', + summary: '错误', + detail: message, + life: 3000, + closable: false + }) + logger.error('检查更新失败:', message) + } + } + + const startDownload = async () => { + downloading.value = true + try { + await downloadUpdateApi() + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + mittbus.emit('toast:add', { + group: 'br', + severity: 'error', + summary: '错误', + detail: message, + life: 3000, + closable: false + }) + logger.error('下载更新失败:', message) + downloading.value = false + } + } + + const quitAndInstall = async () => { + try { + await quitAndInstallApi() + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + mittbus.emit('toast:add', { + group: 'br', + severity: 'error', + summary: '错误', + detail: message, + life: 3000, + closable: false + }) + logger.error('重启安装失败:', message) + } + } + + const init = () => { + registerSubscribe( + subscribeUpdateAvailable(info => { + logger.info('Update available:', info) + updateAvailable.value = true + updateVersion.value = info.version + }) + ) + + registerSubscribe( + subscribeUpdateNotAvailable(() => { + logger.info('Update not available') + updateAvailable.value = false + }) + ) + + registerSubscribe( + subscribeUpdateProgress(progress => { + downloadProgress.value = Math.round(progress.percent) + }) + ) + + registerSubscribe( + subscribeUpdateDownloaded(() => { + logger.info('Update downloaded') + downloading.value = false + updateDownloaded.value = true + }) + ) + + registerSubscribe( + subscribeUpdateError(err => { + logger.error('Update error:', err) + downloading.value = false + updateAvailable.value = false + }) + ) + + checkUpdates() + } + + // Cleanup on unmount is tricky in a store, usually init/destroy are called by component + // or it's a singleton. Assuming component will call init, we should probably handle cleanup there or here if this store is meant to be global and persistent. + // Given the previous component code used onUnmounted, let's expose a destroy method. + const destroy = () => { + unregisterSubscribes() + } + + return { + updateAvailable, + updateDownloaded, + downloading, + downloadProgress, + updateVersion, + checkUpdates, + startDownload, + quitAndInstall, + init, + destroy + } +})