feat(about/update): 优化macOS下载更新交互与关于弹窗按钮布局

- 自动打开目录:macOS下载完成后根据用户是否点了"隐藏到后台"决定是否自动打开下载目录
- 文件校验兜底:打开安装目录失败时清除已下载状态,允许重新下载
- 缓存同步修复:checkForUpdates以后端downloaded字段为准,清除过期的本地ref缓存
- 关于弹窗重构:已下载状态直接显示"打开安装目录"主操作按钮,无需经下载进度中转
- 按钮互斥优化:下载中隐藏"下载更新"和"本次不再提示",显示"下载进度"
- 按钮排版调整:主操作按钮置右侧高亮,各状态下按钮层次分明
This commit is contained in:
Syngnat
2026-03-20 12:55:16 +08:00
parent 1b36f60821
commit 5afd80c559

View File

@@ -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<string | null>(null);
const updateInstallTriggeredVersionRef = React.useRef<string | null>(null);
const updateDownloadMetaRef = React.useRef<UpdateDownloadResultData | null>(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 ? (
<Button key="progress" icon={<DownloadOutlined />} onClick={showUpdateDownloadProgress}></Button>
) : null,
lastUpdateInfo?.hasUpdate && !isLatestUpdateDownloaded ? (
<Button key="download" icon={<DownloadOutlined />} onClick={() => downloadUpdate(lastUpdateInfo, false)}></Button>
) : null,
lastUpdateInfo?.hasUpdate ? (
lastUpdateInfo?.hasUpdate && !isLatestUpdateDownloaded && !isBackgroundProgressForLatestUpdate ? (
<Button key="mute" onClick={() => { updateMutedVersionRef.current = lastUpdateInfo.latestVersion; setIsAboutOpen(false); }}></Button>
) : null,
<Button key="check" icon={<CloudDownloadOutlined />} onClick={() => checkForUpdates(false)}></Button>,
<Button key="close" type="primary" onClick={() => setIsAboutOpen(false)}></Button>
<Button key="close" onClick={() => setIsAboutOpen(false)}></Button>,
lastUpdateInfo?.hasUpdate && !isLatestUpdateDownloaded && !isBackgroundProgressForLatestUpdate ? (
<Button key="download" type="primary" icon={<DownloadOutlined />} onClick={() => downloadUpdate(lastUpdateInfo, false)}></Button>
) : null,
isLatestUpdateDownloaded ? (
<Button key="install-direct" type="primary" icon={<DownloadOutlined />} onClick={handleInstallFromProgress}>
{isMacRuntime ? '打开安装目录' : '安装更新'}
</Button>
) : null,
].filter(Boolean)}
>
{aboutLoading ? (
@@ -2162,7 +2201,10 @@ function App() {
footer={updateDownloadProgress.status === 'start' || updateDownloadProgress.status === 'downloading' ? [
<Button
key="background"
onClick={hideUpdateDownloadProgress}
onClick={() => {
updateUserDismissedRef.current = true;
hideUpdateDownloadProgress();
}}
>
</Button>