From 5afd80c559465af8b51d13b805542d6c5ebce1e9 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Fri, 20 Mar 2026 12:55:16 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(about/update):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96macOS=E4=B8=8B=E8=BD=BD=E6=9B=B4=E6=96=B0=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=E4=B8=8E=E5=85=B3=E4=BA=8E=E5=BC=B9=E7=AA=97=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 自动打开目录:macOS下载完成后根据用户是否点了"隐藏到后台"决定是否自动打开下载目录 - 文件校验兜底:打开安装目录失败时清除已下载状态,允许重新下载 - 缓存同步修复:checkForUpdates以后端downloaded字段为准,清除过期的本地ref缓存 - 关于弹窗重构:已下载状态直接显示"打开安装目录"主操作按钮,无需经下载进度中转 - 按钮互斥优化:下载中隐藏"下载更新"和"本次不再提示",显示"下载进度" - 按钮排版调整:主操作按钮置右侧高亮,各状态下按钮层次分明 --- frontend/src/App.tsx | 62 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b9d22ee..31cf7c5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -657,6 +657,7 @@ function App() { const activeTabId = useStore(state => state.activeTabId); const updateCheckInFlightRef = React.useRef(false); const updateDownloadInFlightRef = React.useRef(false); + const updateUserDismissedRef = React.useRef(false); const updateDownloadedVersionRef = React.useRef(null); const updateInstallTriggeredVersionRef = React.useRef(null); const updateDownloadMetaRef = React.useRef(null); @@ -745,6 +746,7 @@ function App() { return; } updateDownloadInFlightRef.current = true; + updateUserDismissedRef.current = false; updateDownloadMetaRef.current = null; setUpdateDownloadProgress({ open: true, @@ -789,7 +791,18 @@ function App() { } else { void message.success({ content: '更新下载完成', duration: 2 }); } - setAboutUpdateStatus(`发现新版本 ${info.latestVersion}(已下载,请点击“下载进度”后安装)`); + setAboutUpdateStatus(`发现新版本 ${info.latestVersion}(已下载,请点击"下载进度"后安装)`); + // macOS:如果用户没有主动隐藏进度弹窗,则下载完成后自动打开下载目录 + if (isMacRuntime && !updateUserDismissedRef.current) { + try { + const openRes = await (window as any).go.app.App.OpenDownloadedUpdateDirectory(); + if (openRes?.success) { + void message.success(openRes?.message || '已打开安装目录,请手动完成替换'); + } + } catch (e) { + console.warn('自动打开下载目录失败', e); + } + } } else { setUpdateDownloadProgress(prev => ({ ...prev, @@ -820,18 +833,34 @@ function App() { && updateDownloadProgress.version === lastUpdateInfo?.latestVersion && (updateDownloadProgress.status === 'start' || updateDownloadProgress.status === 'downloading' + || updateDownloadProgress.status === 'done' || updateDownloadProgress.status === 'error'); const canShowProgressEntry = (isLatestUpdateDownloaded || isBackgroundProgressForLatestUpdate) && updateInstallTriggeredVersionRef.current !== (lastUpdateInfo?.latestVersion || null); const handleInstallFromProgress = React.useCallback(async () => { - if (updateDownloadProgress.status !== 'done') { + // 允许从下载进度弹窗(status=done)或关于弹窗(isLatestUpdateDownloaded=true)触发 + const canInstall = updateDownloadProgress.status === 'done' + || (Boolean(lastUpdateInfo?.hasUpdate) && (Boolean(lastUpdateInfo?.downloaded) || updateDownloadedVersionRef.current === lastUpdateInfo?.latestVersion)); + if (!canInstall) { return; } if (isMacRuntime) { const res = await (window as any).go.app.App.OpenDownloadedUpdateDirectory(); if (!res?.success) { void message.error('打开安装目录失败: ' + (res?.message || '未知错误')); + // 文件可能已被用户删除,清除已下载状态以允许重新下载 + updateDownloadedVersionRef.current = null; + updateDownloadMetaRef.current = null; + setUpdateDownloadProgress(prev => ({ + ...prev, + status: 'idle', + percent: 0, + downloaded: 0, + open: false, + })); + setLastUpdateInfo(prev => prev ? { ...prev, downloaded: false, downloadPath: undefined } : prev); + setAboutUpdateStatus(prev => prev.replace('已下载', '未下载')); return; } updateInstallTriggeredVersionRef.current = updateDownloadProgress.version || lastUpdateInfo?.latestVersion || null; @@ -846,7 +875,7 @@ function App() { } updateInstallTriggeredVersionRef.current = updateDownloadProgress.version || lastUpdateInfo?.latestVersion || null; hideUpdateDownloadProgress(); - }, [hideUpdateDownloadProgress, isMacRuntime, lastUpdateInfo?.latestVersion, updateDownloadProgress.status, updateDownloadProgress.version]); + }, [hideUpdateDownloadProgress, isMacRuntime, lastUpdateInfo?.latestVersion, lastUpdateInfo?.hasUpdate, lastUpdateInfo?.downloaded, updateDownloadProgress.status, updateDownloadProgress.version]); const checkForUpdates = React.useCallback(async (silent: boolean) => { if (updateCheckInFlightRef.current) return; @@ -867,6 +896,11 @@ function App() { if (!info) return; const aboutOpen = isAboutOpenRef.current; if (info.hasUpdate) { + // 以后端校验为准:如果后端确认文件不存在(downloaded=false),清除本地 ref + if (!info.downloaded && updateDownloadedVersionRef.current === info.latestVersion) { + updateDownloadedVersionRef.current = null; + updateDownloadMetaRef.current = null; + } const localDownloaded = updateDownloadedVersionRef.current === info.latestVersion; const hasDownloaded = Boolean(info.downloaded) || localDownloaded; if (hasDownloaded) { @@ -1719,17 +1753,22 @@ function App() { onCancel={() => setIsAboutOpen(false)} styles={{ content: utilityModalShellStyle, header: { background: 'transparent', borderBottom: 'none', paddingBottom: 8 }, body: { paddingTop: 8 }, footer: { background: 'transparent', borderTop: 'none', paddingTop: 10, display: 'flex', flexWrap: 'wrap', gap: 10, justifyContent: 'flex-end' } }} footer={[ - canShowProgressEntry ? ( + isBackgroundProgressForLatestUpdate && !isLatestUpdateDownloaded ? ( ) : null, - lastUpdateInfo?.hasUpdate && !isLatestUpdateDownloaded ? ( - - ) : null, - lastUpdateInfo?.hasUpdate ? ( + lastUpdateInfo?.hasUpdate && !isLatestUpdateDownloaded && !isBackgroundProgressForLatestUpdate ? ( ) : null, , - + , + lastUpdateInfo?.hasUpdate && !isLatestUpdateDownloaded && !isBackgroundProgressForLatestUpdate ? ( + + ) : null, + isLatestUpdateDownloaded ? ( + + ) : null, ].filter(Boolean)} > {aboutLoading ? ( @@ -2162,7 +2201,10 @@ function App() { footer={updateDownloadProgress.status === 'start' || updateDownloadProgress.status === 'downloading' ? [