🐛 fix(update): 修复 Windows 更新安装卡住并将安装包落到应用目录

- Windows 安装前预检当前安装目录写权限,避免脚本启动后主进程先退出
- 已下载更新补充打开安装目录入口,便于手动执行安装包
- 更新工作区优先改为当前应用运行目录,并补充回归测试与多语言文案
This commit is contained in:
Syngnat
2026-07-05 20:06:51 +08:00
parent 2f39767211
commit 00d70d2934
11 changed files with 204 additions and 8 deletions

View File

@@ -1709,6 +1709,7 @@ function App() {
lastUpdateInfo,
markUpdateProgressDismissed,
muteLatestUpdate,
openDownloadedUpdateDirectory,
setIsAboutOpen,
showUpdateDownloadProgress,
updateChannel,
@@ -4972,7 +4973,12 @@ function App() {
<Button key="check" icon={<CloudDownloadOutlined />} onClick={() => checkForUpdates(false)}>{t('app.about.action.check_updates')}</Button>,
<Button key="close" onClick={() => setIsAboutOpen(false)}>{t('common.close')}</Button>,
lastUpdateInfo?.hasUpdate && !isLatestUpdateDownloaded && !isBackgroundProgressForLatestUpdate ? (
<Button key="download" type="primary" icon={<DownloadOutlined />} onClick={() => downloadUpdate(lastUpdateInfo, false)}>{t('app.about.action.download_update')}</Button>
<Button key="download" type="primary" icon={<DownloadOutlined />} onClick={() => downloadUpdate(lastUpdateInfo, false)}>{t('app.about.action.download_update')}</Button>
) : null,
isLatestUpdateDownloaded ? (
<Button key="open-install-directory" onClick={openDownloadedUpdateDirectory}>
{t('app.about.action.open_install_directory')}
</Button>
) : null,
isLatestUpdateDownloaded ? (
<Button key="install-direct" type="primary" icon={<DownloadOutlined />} onClick={handleInstallFromProgress}>
@@ -6006,6 +6012,9 @@ function App() {
</Button>
] : (updateDownloadProgress.status === 'done' ? [
<Button key="close" onClick={hideUpdateDownloadProgress}>{t('common.close')}</Button>,
<Button key="open-install-directory" onClick={openDownloadedUpdateDirectory}>
{t('app.about.action.open_install_directory')}
</Button>,
<Button key="install" type="primary" onClick={handleInstallFromProgress}>
{t('app.about.action.install_update')}
</Button>

View File

@@ -183,4 +183,34 @@ describe('useAppUpdateManager', () => {
expect(hook?.updateChannel).toBe('dev');
expect(hook?.lastUpdateInfo?.channel).toBe('dev');
});
it('opens the downloaded update directory when a package is already downloaded', async () => {
backendApp.CheckForUpdates.mockResolvedValue({
success: true,
data: {
hasUpdate: true,
currentVersion: '0.8.1',
latestVersion: '0.8.2',
downloaded: true,
assetSize: 1024,
},
});
backendApp.OpenDownloadedUpdateDirectory.mockResolvedValue({
success: true,
message: 'opened-install-directory',
});
renderHook();
await act(async () => {
await hook?.checkForUpdates(false);
});
await act(async () => {
await hook?.openDownloadedUpdateDirectory();
});
expect(backendApp.OpenDownloadedUpdateDirectory).toHaveBeenCalledTimes(1);
expect(messageApi.success).toHaveBeenCalledWith('opened-install-directory');
});
});

View File

@@ -242,6 +242,20 @@ export const useAppUpdateManager = ({
hideUpdateDownloadProgress();
}, [hideUpdateDownloadProgress, lastUpdateInfo, lastUpdateKey, t, updateDownloadProgress.status]);
const openDownloadedUpdateDirectory = useCallback(async () => {
const backendApp = (window as any).go?.app?.App;
if (typeof backendApp?.OpenDownloadedUpdateDirectory !== 'function') {
void message.error(t('app.about.message.open_install_directory_failed_with_error', { error: t('common.unknown') }));
return;
}
const res = await backendApp.OpenDownloadedUpdateDirectory();
if (!res?.success) {
void message.error(t('app.about.message.open_install_directory_failed_with_error', { error: res?.message || t('common.unknown') }));
return;
}
void message.success(res?.message || t('app.about.message.install_directory_opened_manual_replace'));
}, [t]);
const checkForUpdates = useCallback(async (silent: boolean) => {
if (updateCheckInFlightRef.current) return;
updateCheckInFlightRef.current = true;
@@ -523,6 +537,7 @@ export const useAppUpdateManager = ({
lastUpdateInfo,
markUpdateProgressDismissed,
muteLatestUpdate,
openDownloadedUpdateDirectory,
setIsAboutOpen,
showUpdateDownloadProgress,
updateChannel,