From 1fb09274cacd06f9bd7eeafade4c6ae5d06a0a3e Mon Sep 17 00:00:00 2001 From: jxxghp Date: Mon, 7 Sep 2026 13:37:32 +0800 Subject: [PATCH] fix: separate auto-update and dev tracking settings for clarity by jxxghp --- src/api/types.ts | 4 ++ src/components/system/SystemUpdatePrompt.vue | 3 ++ .../__tests__/SystemUpdatePrompt.spec.ts | 39 +++++++++++++++++++ src/locales/en-US.ts | 3 ++ src/locales/zh-CN.ts | 2 + src/locales/zh-TW.ts | 2 + src/views/setting/AccountSettingSystem.vue | 18 +++++---- .../__tests__/AccountSettingSystem.spec.ts | 29 +++++++++++++- 8 files changed, 90 insertions(+), 10 deletions(-) diff --git a/src/api/types.ts b/src/api/types.ts index eb878c49..e667d17a 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -2651,6 +2651,10 @@ export interface SystemUpdateItemStatus { /** 后端主程序与站点资源后台更新状态机快照。 */ export interface SystemUpdateStatus { + /** 当前自动检查开关;由后端实时返回,避免旧缓存继续触发提醒。 */ + auto_update?: boolean + /** 站点资源单独控制自动检查和提醒。 */ + auto_update_resource?: boolean state: SystemUpdateState current_version: string version?: string | null diff --git a/src/components/system/SystemUpdatePrompt.vue b/src/components/system/SystemUpdatePrompt.vue index 7e3c81d2..586f8131 100644 --- a/src/components/system/SystemUpdatePrompt.vue +++ b/src/components/system/SystemUpdatePrompt.vue @@ -70,6 +70,9 @@ const updateItems = computed(() => { const visibleItems = computed(() => updateItems.value.filter(item => { + // 关闭自动检查后隐藏版本提醒,手动发起的下载、安装和失败反馈仍可见。 + const enabled = item.type === 'resources' ? status.value?.auto_update_resource : status.value?.auto_update + if (['available', 'ready'].includes(item.state) && enabled !== true) return false if (!['available', 'downloading', 'ready', 'installing', 'failed'].includes(item.state)) return false return !['available', 'ready'].includes(item.state) || !isCurrentVersionSuppressed(item) }), diff --git a/src/components/system/__tests__/SystemUpdatePrompt.spec.ts b/src/components/system/__tests__/SystemUpdatePrompt.spec.ts index ab9c186f..0c66f10a 100644 --- a/src/components/system/__tests__/SystemUpdatePrompt.spec.ts +++ b/src/components/system/__tests__/SystemUpdatePrompt.spec.ts @@ -57,6 +57,8 @@ vi.mock('vue-i18n', async importOriginal => ({ })) const availableStatus: SystemUpdateStatus = { + auto_update: true, + auto_update_resource: true, state: 'available', current_version: 'v3.0.0', version: 'v3.1.0', @@ -77,6 +79,43 @@ describe('SystemUpdatePrompt', () => { mocks.footerDockHeight!.value = null }) + it.each(['available', 'ready'] as const)( + 'hides cached %s reminders when automatic checks are disabled', + async state => { + mocks.updateStatus!.value = { ...availableStatus, state, auto_update: false } + await renderWithProviders(SystemUpdatePrompt, { props: { enabled: true } }) + expect(document.querySelector('.system-update-prompt')).not.toBeInTheDocument() + + mocks.updateStatus!.value = { ...availableStatus, state, auto_update: true } + await waitFor(() => expect(document.querySelector('.system-update-prompt')).toBeInTheDocument()) + mocks.updateStatus!.value = { ...availableStatus, state, auto_update: false } + await waitFor(() => expect(document.querySelector('.system-update-prompt')).not.toBeInTheDocument()) + }, + ) + + it('keeps manually started download progress visible with automatic checks disabled', async () => { + mocks.updateStatus!.value = { ...availableStatus, state: 'downloading', auto_update: false, progress: 25 } + await renderWithProviders(SystemUpdatePrompt, { props: { enabled: true } }) + expect(screen.getByText('25%')).toBeInTheDocument() + }) + + it('shows only resource reminders when only automatic resource updates are enabled', async () => { + mocks.updateStatus!.value = { + ...availableStatus, + auto_update: false, + auto_update_resource: true, + updates: [ + { ...availableStatus, type: 'application' }, + { ...availableStatus, type: 'resources', auth_version: '3.1.0' }, + ], + } + await renderWithProviders(SystemUpdatePrompt, { props: { enabled: true } }) + expect(screen.queryByText('systemUpdate.applicationAvailableTitle')).not.toBeInTheDocument() + expect(screen.getByText('systemUpdate.resourcesAvailableTitle')).toBeInTheDocument() + mocks.updateStatus!.value = { ...mocks.updateStatus!.value, auto_update_resource: false } + await waitFor(() => expect(document.querySelector('.system-update-prompt')).not.toBeInTheDocument()) + }) + it('asks an administrator to start the background download', async () => { mocks.post.mockResolvedValue({ ...availableStatus, diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 99e09885..4a2ae56a 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -2989,6 +2989,9 @@ export default { imageProxyAllowedPrivateRangeAdd: 'Add CIDR, e.g.: 198.18.0.0/15', proxyHost: 'Proxy Server', proxyHostHint: 'Set proxy server address, support: http(s), socks5, socks5h, etc.', + moviePilotAutoUpdate: 'Automatically check for updates', + moviePilotAutoUpdateHint: + 'Check for application updates every 6 hours and show reminders; confirm downloads and installation manually', moviePilotDevUpdate: 'Track Dev builds', moviePilotDevUpdateHint: 'Update to the latest code on the current v3 development branch at every restart', autoUpdateResource: 'Auto Update Resource', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index f2be3653..97a373fa 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -2924,6 +2924,8 @@ export default { imageProxyAllowedPrivateRangeAdd: '添加 CIDR,如:198.18.0.0/15', proxyHost: '代理服务器', proxyHostHint: '设置代理服务器地址,支持:http(s)、socks5、socks5h 等协议', + moviePilotAutoUpdate: '自动检查版本更新', + moviePilotAutoUpdateHint: '每 6 小时检查主程序版本更新并提示,下载和安装需手动确认', moviePilotDevUpdate: '跟踪 Dev 开发版', moviePilotDevUpdateHint: '每次重启时更新到当前 v3 开发分支的最新代码', autoUpdateResource: '自动更新站点资源', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index f3fd7c5a..6577ec69 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -2897,6 +2897,8 @@ export default { imageProxyAllowedPrivateRangeAdd: '添加 CIDR,如:198.18.0.0/15', proxyHost: '代理服務器', proxyHostHint: '設置代理服務器地址,支持:http(s)、socks5、socks5h 等協議', + moviePilotAutoUpdate: '自動檢查版本更新', + moviePilotAutoUpdateHint: '每 6 小時檢查主程式版本更新並提示,下載和安裝需手動確認', moviePilotDevUpdate: '追蹤 Dev 開發版', moviePilotDevUpdateHint: '每次重新啟動時更新到目前 v3 開發分支的最新程式碼', autoUpdateResource: '自動更新站點資源', diff --git a/src/views/setting/AccountSettingSystem.vue b/src/views/setting/AccountSettingSystem.vue index bcc6cf9a..ab454837 100644 --- a/src/views/setting/AccountSettingSystem.vue +++ b/src/views/setting/AccountSettingSystem.vue @@ -99,6 +99,7 @@ const SystemSettings = ref({ DB_WAL_ENABLE: false, AUTO_UPDATE_RESOURCE: true, MOVIEPILOT_AUTO_UPDATE: false, + MOVIEPILOT_UPDATE_DEV: false, DATA_CLEANUP_ENABLE: false, DATA_CLEANUP_MESSAGE_DAYS: 90, DATA_CLEANUP_DOWNLOAD_HISTORY_DAYS: 180, @@ -1117,13 +1118,6 @@ function onMediaServerChange(mediaserver: MediaServerConf, name: string) { if (index !== -1) mediaServers.value[index] = mediaserver } -const moviePilotDevUpdate = computed({ - get: () => SystemSettings.value.Advanced.MOVIEPILOT_AUTO_UPDATE === 'dev', - set: enabled => { - SystemSettings.value.Advanced.MOVIEPILOT_AUTO_UPDATE = enabled ? 'dev' : 'false' - }, -}) - // Fanart语言多选处理 const fanartLanguageSelection = computed({ get: () => { @@ -2131,7 +2125,15 @@ watch(currentLlmSnapshotKey, (snapshotKey, previousSnapshotKey) => { + + + { expect(mocks.toastSuccess).toHaveBeenCalledWith('高级设置保存成功') }) - it('round-trips all advanced system switches and enables only the Dev update mode', async () => { + it.each([ + [false, false], + [false, true], + [true, false], + [true, true], + ])('loads and saves independent update switches (%s, %s)', async (autoUpdate, updateDev) => { + systemEnv.MOVIEPILOT_AUTO_UPDATE = autoUpdate + systemEnv.MOVIEPILOT_UPDATE_DEV = updateDev + await renderSettings() + const dialog = await openAdvancedTab('系统') + const autoSwitch = dialog.getByLabelText('自动检查版本更新') + const devSwitch = dialog.getByLabelText('跟踪 Dev 开发版') + expect((autoSwitch as HTMLInputElement).checked).toBe(autoUpdate) + expect((devSwitch as HTMLInputElement).checked).toBe(updateDev) + await fireEvent.click(autoSwitch) + expect((devSwitch as HTMLInputElement).checked).toBe(updateDev) + await fireEvent.click(dialog.getByRole('button', { name: '保存' })) + await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()) + expect(findPost('system/env')?.[1]).toEqual( + expect.objectContaining({ MOVIEPILOT_AUTO_UPDATE: !autoUpdate, MOVIEPILOT_UPDATE_DEV: updateDev }), + ) + }) + + it('round-trips all advanced system switches with independent boolean update settings', async () => { await renderSettings() const dialog = await openAdvancedTab('系统') for (const label of [ @@ -1018,6 +1041,7 @@ describe('AccountSettingSystem', () => { '分享工作流数据', '大内存模式', '数据库WAL模式', + '自动检查版本更新', '跟踪 Dev 开发版', '自动更新站点资源', ]) { @@ -1033,7 +1057,8 @@ describe('AccountSettingSystem', () => { BIG_MEMORY_MODE: true, DB_WAL_ENABLE: true, GLOBAL_IMAGE_CACHE: true, - MOVIEPILOT_AUTO_UPDATE: 'dev', + MOVIEPILOT_AUTO_UPDATE: true, + MOVIEPILOT_UPDATE_DEV: true, PLUGIN_STATISTIC_SHARE: false, SUBSCRIBE_STATISTIC_SHARE: false, USAGE_STATISTIC_SHARE: false,