From 06de88a367d6d94265af31c1067ffcdc6b9263aa Mon Sep 17 00:00:00 2001 From: zipg Date: Sun, 11 Oct 2026 12:47:46 +0800 Subject: [PATCH] fix(grid): preserve view state across result tab switches (#11521) Co-authored-by: zipg <4047349+zipg@users.noreply.github.com> --- .../src/components/layout/ContentArea.vue | 13 ++++++++ .../ContentArea.resultTabViewState.spec.ts | 32 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 apps/desktop/src/components/layout/__tests__/ContentArea.resultTabViewState.spec.ts diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index e0288ba2e4..9d07c34531 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -1639,6 +1639,7 @@ async function closeCurrentQueryResult() { } async function selectResultRun(runId: string) { + if (runId !== props.activeTab.activeResultRunId) dispatchBeforeResultTabSwitch(); if (!(await queryStore.setActiveResultRun(props.activeTab.id, runId))) { toast(t("tabs.missingResultRun"), 4000); return false; @@ -1647,6 +1648,17 @@ async function selectResultRun(runId: string) { return true; } +/** + * Result-set tabs share one query tab, but switching them still replaces the + * mounted DataGrid instance. Give the grid the same pre-switch boundary used + * by editor tabs so it snapshots its viewport and selection before the store + * changes the active result. + */ +function dispatchBeforeResultTabSwitch() { + if (typeof window === "undefined") return; + window.dispatchEvent(new CustomEvent("dbx:before-tab-switch", { detail: { tabId: props.activeTab.id, fromTabId: props.activeTab.id } })); +} + /** * 点击结果标签:切换显示的同时固定该结果(issue #9975)。 * 未固定的结果会被下一次普通查询复用/覆盖,点击后固定即可保留下来。 @@ -1686,6 +1698,7 @@ function toggleResultAutoSave() { } function selectResultItem(item: (typeof visibleResultItems.value)[number]) { + if (item.index !== (props.activeTab.activeResultIndex ?? 0)) dispatchBeforeResultTabSwitch(); queryStore.setActiveResultIndex(props.activeTab.id, item.index); emit("update:activeOutputView", props.activeTab.id, "result"); const range = resultSourceRange(props.activeTab.sql, item.result, item.index, activeEffectiveDatabaseType.value, activeSqlStatementParameterOptions.value) ?? null; diff --git a/apps/desktop/src/components/layout/__tests__/ContentArea.resultTabViewState.spec.ts b/apps/desktop/src/components/layout/__tests__/ContentArea.resultTabViewState.spec.ts new file mode 100644 index 0000000000..7e93d0b017 --- /dev/null +++ b/apps/desktop/src/components/layout/__tests__/ContentArea.resultTabViewState.spec.ts @@ -0,0 +1,32 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const contentAreaSource = readFileSync(new URL("../ContentArea.vue", import.meta.url), "utf8"); + +function functionSource(name: string, endMarker: string): string { + const start = contentAreaSource.indexOf(`function ${name}(`); + const end = contentAreaSource.indexOf(endMarker, start + 1); + expect(start).toBeGreaterThanOrEqual(0); + expect(end).toBeGreaterThan(start); + return contentAreaSource.slice(start, end); +} + +describe("ContentArea result-tab view state handoff", () => { + it("dispatches the pre-switch boundary before changing the result index", () => { + const source = functionSource("selectResultItem", "function executionSummaryItemRange("); + expect(source.indexOf("dispatchBeforeResultTabSwitch()")).toBeGreaterThanOrEqual(0); + expect(source.indexOf("dispatchBeforeResultTabSwitch()")).toBeLessThan(source.indexOf("queryStore.setActiveResultIndex")); + }); + + it("dispatches the pre-switch boundary before changing the result run", () => { + const source = functionSource("selectResultRun", "/**\n * 点击结果标签"); + expect(source.indexOf("dispatchBeforeResultTabSwitch()")).toBeGreaterThanOrEqual(0); + expect(source.indexOf("dispatchBeforeResultTabSwitch()")).toBeLessThan(source.indexOf("queryStore.setActiveResultRun")); + }); + + it("uses the shared before-tab-switch event consumed by DataGrid", () => { + const helper = functionSource("dispatchBeforeResultTabSwitch", "async function selectResultRunFromTab"); + expect(helper).toContain('new CustomEvent("dbx:before-tab-switch"'); + expect(helper).toContain("fromTabId: props.activeTab.id"); + }); +});