diff --git a/src/App.tsx b/src/App.tsx index 8bd03438..1565e8b3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -196,6 +196,7 @@ function App() { setJustUpdatedInfo, setShowInstallConfirmModal, showInstallConfirmModal, + quitAfterRun, updateInfo, downloadStatus, setShowUpdateDialog, @@ -238,6 +239,7 @@ function App() { setJustUpdatedInfo: state.setJustUpdatedInfo, setShowInstallConfirmModal: state.setShowInstallConfirmModal, showInstallConfirmModal: state.showInstallConfirmModal, + quitAfterRun: state.quitAfterRun, updateInfo: state.updateInfo, downloadStatus: state.downloadStatus, setShowUpdateDialog: state.setShowUpdateDialog, @@ -414,6 +416,9 @@ function App() { // 尝试自动安装更新(无任务运行中时触发) const tryAutoInstallUpdate = useCallback(() => { const state = useAppStore.getState(); + // 本次启动实际启用 -q 自动任务时优先退出,更新留给下次普通启动安装。 + // 启动策略尚未确定时也不安装,避免初始化期间抢先触发。 + if (state.quitAfterRun !== false) return; if (state.downloadStatus !== 'completed') return; if (state.installStatus !== 'idle') return; if (state.autoInstallPending) return; @@ -476,9 +481,7 @@ function App() { if (result.success) { // 使用实际保存路径(可能与请求路径不同,如果从 302 重定向检测到正确文件名) setDownloadSavePath(result.actualSavePath); - setDownloadStatus('completed'); log.info('更新下载完成'); - // 保存待安装更新信息,以便下次启动时自动安装 savePendingUpdateInfo({ versionName: updateResult.versionName, @@ -491,6 +494,9 @@ function App() { timestamp: Date.now(), }); + // 先持久化待安装信息,再通知下载完成,避免自动安装先于保存执行。 + setDownloadStatus('completed'); + // 尝试自动安装更新 tryAutoInstallUpdate(); } else { @@ -592,6 +598,14 @@ function App() { setErrorMessage(''); try { + let shouldQuit = false; + if (isTauri()) { + try { + shouldQuit = await invoke('has_quit_after_run_flag'); + } catch (err) { + log.warn('读取 --quit-after-run 参数失败,按普通启动继续初始化:', err); + } + } log.info('加载 interface.json...'); const result = await autoLoadInterface(); setProjectInterface(result.interface); @@ -876,6 +890,7 @@ function App() { // 新版本下载在后台进行,不影响本次任务启动。 let autoStartTasksPending = false; let isAutoRunOnLaunchMode = false; + let unsubscribeQuitAfterRun: (() => void) | undefined; if (isTauri()) { try { const isAutoStart = await invoke('is_autostart'); @@ -917,16 +932,16 @@ function App() { } useAppStore.getState().setActiveInstance(targetInstanceId); - // 检查 -q/--quit-after-run 参数:任务完成后关闭自身 - const shouldQuit = await invoke('has_quit_after_run_flag'); + // -q/--quit-after-run:任务完成后关闭自身 if (shouldQuit) { log.info('命令行 --quit-after-run 参数:任务完成后将关闭自身'); - const unsub = useAppStore.subscribe( + unsubscribeQuitAfterRun = useAppStore.subscribe( (state) => state.instances.find((i) => i.id === targetInstanceId)?.isRunning, (isRunning, prevIsRunning) => { if (prevIsRunning && !isRunning) { log.info('自动执行任务完成,关闭自身'); - unsub(); + unsubscribeQuitAfterRun?.(); + unsubscribeQuitAfterRun = undefined; import('@tauri-apps/plugin-process').then(({ exit }) => exit(0)); } }, @@ -943,6 +958,10 @@ function App() { } } + // -q 仅在本次启动将自动执行有效实例时生效,否则沿用普通更新流程。 + shouldQuit = shouldQuit && autoStartTasksPending; + useAppStore.getState().setQuitAfterRun(shouldQuit); + const dispatchPendingAutoStartTasks = () => { if (!autoStartTasksPending) return; autoStartTasksPending = false; @@ -952,8 +971,14 @@ function App() { new CustomEvent('mxu-start-tasks', { detail: { source: 'autostart', - onSettled: () => { + onSettled: (started: boolean) => { autoTaskLaunchPendingRef.current = false; + if (!started) { + // 空任务、连接失败等情况不会产生任务结束事件,恢复普通更新流程。 + unsubscribeQuitAfterRun?.(); + unsubscribeQuitAfterRun = undefined; + useAppStore.getState().setQuitAfterRun(false); + } // 启动失败时任务不会产生 running -> idle 状态变化,需要在这里补一次安装检查; // 启动成功时仍处于运行态,本次检查会直接返回,并在任务结束后再次触发。 tryAutoInstallUpdate(); @@ -993,9 +1018,9 @@ function App() { normalizeVersion(currentVersionNow) === normalizeVersion(updateCompleteInfo.newVersion) ) { log.info('版本已更新到目标版本'); - if (isAutoStartModeNow) { + if (isAutoStartModeNow || shouldQuit) { // 无人值守:跳过弹窗,继续检查是否有更新版本 - log.info('自启动模式,跳过更新完成弹窗'); + log.info('自动执行模式,跳过更新完成弹窗'); } else { showUpdateCompletedUI(); if (isAutoRunOnLaunchMode) { @@ -1010,8 +1035,8 @@ function App() { // 直接显示更新完成弹窗(zip 等自动安装场景) log.info('检测到刚更新完成:', updateCompleteInfo.newVersion); clearPendingUpdateInfo(); - if (isAutoStartModeNow) { - log.info('自启动模式,跳过更新完成弹窗'); + if (isAutoStartModeNow || shouldQuit) { + log.info('自动执行模式,跳过更新完成弹窗'); } else { showUpdateCompletedUI(); if (isAutoRunOnLaunchMode) { @@ -1039,6 +1064,13 @@ function App() { downloadSource: pendingUpdate.downloadSource, }); setDownloadSavePath(pendingUpdate.downloadSavePath); + if (shouldQuit) { + // 托管运行不安装、不重启;保留待安装包供调用方另开更新会话。 + setDownloadStatus('completed'); + log.info('--quit-after-run 模式,保留待安装更新并继续执行任务'); + dispatchPendingAutoStartTasks(); + return; + } // 先设置 installStatus,再设置 downloadStatus, // 避免 subscribe 在 installStatus 还是 idle 时触发 tryAutoInstallUpdate 造成双发 useAppStore.getState().setInstallStatus('installing'); @@ -1378,7 +1410,7 @@ function App() { useEffect(() => { if (currentPage === 'main') { // 下载完成:弹出安装模态框 - if (downloadStatus === 'completed') { + if (downloadStatus === 'completed' && quitAfterRun === false) { setShowInstallConfirmModal(true); } // 有更新、有错误或正在下载:弹出更新气泡 @@ -1391,16 +1423,17 @@ function App() { updateInfo?.hasUpdate, updateInfo?.errorCode, downloadStatus, + quitAfterRun, setShowUpdateDialog, setShowInstallConfirmModal, ]); // 下载完成时,强制弹出安装模态框 useEffect(() => { - if (downloadStatus === 'completed') { + if (downloadStatus === 'completed' && quitAfterRun === false) { setShowInstallConfirmModal(true); } - }, [downloadStatus, setShowInstallConfirmModal]); + }, [downloadStatus, quitAfterRun, setShowInstallConfirmModal]); // 下载失败或取消时,如果有挂起的自动任务,立即分发 useEffect(() => { diff --git a/src/components/UpdatePanel.tsx b/src/components/UpdatePanel.tsx index e3e3bf32..5f5dabbf 100644 --- a/src/components/UpdatePanel.tsx +++ b/src/components/UpdatePanel.tsx @@ -80,7 +80,6 @@ export function UpdatePanel({ onClose, anchorRef }: UpdatePanelProps) { if (result.success) { // 使用实际保存路径(可能与请求路径不同,如果从 302 重定向检测到正确文件名) setDownloadSavePath(result.actualSavePath); - setDownloadStatus('completed'); // 保存待安装更新信息,以便下次启动时自动安装 savePendingUpdateInfo({ versionName: updateInfo.versionName, @@ -92,6 +91,7 @@ export function UpdatePanel({ onClose, anchorRef }: UpdatePanelProps) { downloadSource: updateInfo.downloadSource, timestamp: Date.now(), }); + setDownloadStatus('completed'); } else { setDownloadStatus('failed'); } diff --git a/src/components/settings/UpdateSection.tsx b/src/components/settings/UpdateSection.tsx index bd9aaf3e..17b7ca52 100644 --- a/src/components/settings/UpdateSection.tsx +++ b/src/components/settings/UpdateSection.tsx @@ -25,6 +25,7 @@ import { cancelDownload, MIRRORCHYAN_ERROR_CODES, isDebugVersion, + savePendingUpdateInfo, } from '@/services/updateService'; import { createProxySettings, proxySettingsForUpdateDownload } from '@/services/proxyService'; import { resolveI18nText } from '@/services/contentResolver'; @@ -152,6 +153,16 @@ export function UpdateSection() { if (result.success) { // 使用实际保存路径(可能与请求路径不同,如果从 302 重定向检测到正确文件名) setDownloadSavePath(result.actualSavePath); + savePendingUpdateInfo({ + versionName: info.versionName, + releaseNote: info.releaseNote, + channel: info.channel, + downloadSavePath: result.actualSavePath, + fileSize: info.fileSize, + updateType: info.updateType, + downloadSource: info.downloadSource, + timestamp: Date.now(), + }); setDownloadStatus('completed'); } else { setDownloadStatus('failed'); diff --git a/src/services/updateService.ts b/src/services/updateService.ts index b98f5552..fb4c6f8f 100644 --- a/src/services/updateService.ts +++ b/src/services/updateService.ts @@ -1223,7 +1223,7 @@ export function consumeUpdateCompleteInfo(): UpdateCompleteInfo | null { } /** - * 保存待安装更新信息到本地存储(下载完成后调用) + * 保存待安装更新信息到本地存储(下载完成后调用)。失败时抛出,由下载入口处理。 */ export function savePendingUpdateInfo(info: PendingUpdateInfo): void { try { @@ -1231,6 +1231,7 @@ export function savePendingUpdateInfo(info: PendingUpdateInfo): void { log.info('已保存待安装更新信息:', info.versionName); } catch (error) { log.warn('保存待安装更新信息失败:', error); + throw error; } } diff --git a/src/stores/appStore.ts b/src/stores/appStore.ts index 7966539d..69b589e6 100644 --- a/src/stores/appStore.ts +++ b/src/stores/appStore.ts @@ -2136,6 +2136,8 @@ export const useAppStore = create()( }), // 安装状态 + quitAfterRun: null, + setQuitAfterRun: (quit) => set({ quitAfterRun: quit }), showInstallConfirmModal: false, installStatus: 'idle', installError: null, diff --git a/src/stores/types.ts b/src/stores/types.ts index 6ba8cf33..f0679595 100644 --- a/src/stores/types.ts +++ b/src/stores/types.ts @@ -470,6 +470,9 @@ export interface AppState { resetDownloadState: () => void; // 安装状态 + /** 本次启动是否实际启用 -q 自动任务;null 表示尚未确定,此时不自动安装。仅运行时使用。 */ + quitAfterRun: boolean | null; + setQuitAfterRun: (quit: boolean) => void; showInstallConfirmModal: boolean; installStatus: InstallStatus; installError: string | null;