From 9b747c67a9c6df2231a72b4081e6e894a40e2484 Mon Sep 17 00:00:00 2001 From: InfinityPacer <160988576+InfinityPacer@users.noreply.github.com> Date: Fri, 21 Aug 2026 18:29:23 +0800 Subject: [PATCH] fix(plugins): reconcile runtime changes after tab switch (#695) --- src/views/plugin/PluginCardListView.vue | 21 +++++++++++++++- .../__tests__/PluginCardListView.spec.ts | 24 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/views/plugin/PluginCardListView.vue b/src/views/plugin/PluginCardListView.vue index cefc9165..45c7fe52 100644 --- a/src/views/plugin/PluginCardListView.vue +++ b/src/views/plugin/PluginCardListView.vue @@ -218,6 +218,7 @@ const isMarketRefreshing = ref(false) const pluginRuntimeSummary = computed(() => pluginRuntimeStore.summary) const installingPluginIds = ref(new Set()) const isPluginPageActive = ref(false) +const appliedRuntimeReconciliation = ref(0) // 每类远程快照独立管理 writer 代际,旧请求只能完成自身 Promise,不能覆盖新状态。 let installedWriterGeneration = 0 @@ -1038,6 +1039,17 @@ async function fetchInstalledPlugins(context: KeepAliveRefreshContext = {}): Pro } } +/** 运行态变化在市场页发生时延后应用,回到已安装列表后再补一次快照。 */ +async function reconcileInstalledRuntime(): Promise { + const reconciliation = pluginRuntimeStore.reconciliation + if (reconciliation <= appliedRuntimeReconciliation.value) return + + const refreshed = await fetchInstalledPlugins({ silent: true }) + if (refreshed && reconciliation === pluginRuntimeStore.reconciliation) { + appliedRuntimeReconciliation.value = reconciliation + } +} + function isPluginRuntimeSettling(pluginId: string) { return pluginRuntimeSummary.value?.ready === false || installingPluginIds.value.has(pluginId) } @@ -1448,19 +1460,26 @@ watch(activeTab, (newTab, oldTab) => { if (generation !== tabScrollRestoreGeneration) return window.scrollTo({ behavior: 'auto', top: tabScrollPositions[newTab] }) }) + + if (newTab === 'installed' && isPluginPageActive.value && !document.hidden) { + void reconcileInstalledRuntime() + } }) watch( () => pluginRuntimeStore.reconciliation, () => { if (isPluginPageActive.value && activeTab.value === 'installed' && !document.hidden) { - void fetchInstalledPlugins({ silent: true }) + void reconcileInstalledRuntime() } }, ) onActivated(() => { isPluginPageActive.value = true + if (activeTab.value === 'installed' && !document.hidden) { + void reconcileInstalledRuntime() + } }) onDeactivated(() => { diff --git a/src/views/plugin/__tests__/PluginCardListView.spec.ts b/src/views/plugin/__tests__/PluginCardListView.spec.ts index d22f075b..75dcb6bc 100644 --- a/src/views/plugin/__tests__/PluginCardListView.spec.ts +++ b/src/views/plugin/__tests__/PluginCardListView.spec.ts @@ -794,6 +794,30 @@ describe('PluginCardListView loading and request ownership', () => { await waitForRequestsToFinish() }) + it('applies a runtime generation received while the market tab is open', async () => { + let installedRequests = 0 + const { pinia } = await renderList({ + installed: () => { + installedRequests += 1 + return [createPlugin({ id: 'Installed', installed: true, plugin_name: '已安装插件' })] + }, + market: () => [createPlugin({ id: 'Market', plugin_name: '市场插件' })], + }) + await waitForRequestsToFinish() + const initialInstalledRequests = installedRequests + const runtimeStore = usePluginRuntimeStore(pinia) + + getHeaderConfig().modelValue.value = 'market' + await nextTick() + runtimeStore.reconciliation = 1 + await nextTick() + expect(installedRequests).toBe(initialInstalledRequests) + + getHeaderConfig().modelValue.value = 'installed' + await waitFor(() => expect(installedRequests).toBe(initialInstalledRequests + 1)) + await waitForRequestsToFinish() + }) + it('does not request the superuser runtime summary for an ordinary administrator', async () => { server.use( http.get(apiUrls.runtime, () => {