feat(query): pin, rename and label result run tabs

This commit is contained in:
高桥凉介 authored and GitHub committed 2026-09-24 17:18:12 +08:00
1 parent a593a2b200
commit c5eb0a7a4a
26 files changed
+319 -32

No files matched your search

@@ -661,6 +661,7 @@ const debugLogCopied = ref(false);
const debugLogDownloaded = ref(false);
const editShowColumnCommentsInHeader = ref(settingsStore.editorSettings.showColumnCommentsInHeader);
const editShowColumnTypesInHeader = ref(settingsStore.editorSettings.showColumnTypesInHeader);
const editShowResultSourceDatabase = ref(settingsStore.editorSettings.showResultSourceDatabase);
const editDataGridShowTransposeFieldMetadata = ref(settingsStore.editorSettings.dataGridShowTransposeFieldMetadata);
const editColorizeDataGridCellTypes = ref(settingsStore.editorSettings.colorizeDataGridCellTypes);
const editShowIndexIndicatorsInHeader = ref(settingsStore.editorSettings.showIndexIndicatorsInHeader);
@@ -1002,6 +1003,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
tabSortMode: editTabSortMode.value,
showColumnCommentsInHeader: editShowColumnCommentsInHeader.value,
showColumnTypesInHeader: editShowColumnTypesInHeader.value,
showResultSourceDatabase: editShowResultSourceDatabase.value,
dataGridShowTransposeFieldMetadata: editDataGridShowTransposeFieldMetadata.value,
colorizeDataGridCellTypes: editColorizeDataGridCellTypes.value,
dataGridTypeColorSchemes: editDataGridTypeColorSchemes.value,
@@ -1647,6 +1649,7 @@ function syncEditorSettingsDraftFromStore() {
editTabSortMode.value = settingsStore.editorSettings.tabSortMode;
editShowColumnCommentsInHeader.value = settingsStore.editorSettings.showColumnCommentsInHeader;
editShowColumnTypesInHeader.value = settingsStore.editorSettings.showColumnTypesInHeader;
editShowResultSourceDatabase.value = settingsStore.editorSettings.showResultSourceDatabase;
editDataGridShowTransposeFieldMetadata.value = settingsStore.editorSettings.dataGridShowTransposeFieldMetadata;
editColorizeDataGridCellTypes.value = settingsStore.editorSettings.colorizeDataGridCellTypes;
editDataGridTypeColorSchemes.value = cloneDataGridTypeColorSchemes(settingsStore.editorSettings.dataGridTypeColorSchemes);
@@ -1780,6 +1783,7 @@ const editorSettingsDraftRefs: EditorSettingsDraftRefMap = {
tabSortMode: editTabSortMode,
showColumnCommentsInHeader: editShowColumnCommentsInHeader,
showColumnTypesInHeader: editShowColumnTypesInHeader,
showResultSourceDatabase: editShowResultSourceDatabase,
dataGridShowTransposeFieldMetadata: editDataGridShowTransposeFieldMetadata,
colorizeDataGridCellTypes: editColorizeDataGridCellTypes,
dataGridTypeColorSchemes: editDataGridTypeColorSchemes,
@@ -2304,6 +2308,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
historyRetention.reset();
editShowColumnCommentsInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader;
editShowColumnTypesInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnTypesInHeader;
editShowResultSourceDatabase.value = DEFAULT_EDITOR_SETTINGS.showResultSourceDatabase;
editDataGridShowTransposeFieldMetadata.value = DEFAULT_EDITOR_SETTINGS.dataGridShowTransposeFieldMetadata;
editColorizeDataGridCellTypes.value = DEFAULT_EDITOR_SETTINGS.colorizeDataGridCellTypes;
// Back to the built-in palette, but keep the user's saved schemes available.
@@ -7908,6 +7913,17 @@ onUnmounted(() => {
</div>
<Switch id="show-column-types-in-header" v-model="editShowColumnTypesInHeader" />
</div>
<div class="settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="show-result-source-database">
{{ t("settings.showResultSourceDatabase") }}
</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.showResultSourceDatabaseDescription") }}
</p>
</div>
<Switch id="show-result-source-database" v-model="editShowResultSourceDatabase" />
</div>
<div class="settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="data-grid-show-transpose-field-metadata">
@@ -494,8 +494,10 @@ const activeQueryError = computed(() => {
return String(result.rows[0]?.[0] ?? "");
});
const hasQueryOutput = computed(() => tabHasQueryOutput(props.activeTab));
const visibleResultItems = computed(() => tabularResultItems(props.activeTab.results ?? (props.activeTab.result ? [props.activeTab.result] : undefined)));
const tabularResults = computed(() => tabularResultItems(props.activeTab.results));
// 结果集页签/列表的名称是否带库名,由编辑器设置控制(默认带库名)
const includeResultSourceDatabase = computed(() => settingsStore.editorSettings.showResultSourceDatabase);
const visibleResultItems = computed(() => tabularResultItems(props.activeTab.results ?? (props.activeTab.result ? [props.activeTab.result] : undefined), { includeSourceDatabase: includeResultSourceDatabase.value }));
const tabularResults = computed(() => tabularResultItems(props.activeTab.results, { includeSourceDatabase: includeResultSourceDatabase.value }));
const allResultExportSheets = computed(() =>
tabularResults.value.map((item) => ({
sheetName: item.label || t("tabs.resultN", { n: item.n }),
@@ -503,7 +505,14 @@ const allResultExportSheets = computed(() =>
sql: item.index === props.activeTab.activeResultIndex ? queryResultExecutionSql(props.activeTab) : item.result.sourceStatement,
})),
);
const resultRuns = computed(() => resultRunItems(props.activeTab));
// 结果标签优先显示来源(表名),历史批次缺少来源信息时按批次 SQL 重新解析
const resultRuns = computed(() =>
resultRunItems(props.activeTab, {
includeSourceDatabase: includeResultSourceDatabase.value,
database: props.activeTab.database,
databaseType: activeEffectiveDatabaseType.value,
}),
);
const activeResultGridCacheKey = computed(() => resultGridCacheKey(props.activeTab));
const activeResultGridColumnWidthCacheKey = computed(() => resultGridColumnWidthCacheKey(props.activeTab));
const activeResultGridInstanceKey = computed(() => resultGridInstanceKey(props.activeTab));
@@ -1243,7 +1252,7 @@ async function closeResultRunsToRight(runId: string) {
function openResultRunRename(run: (typeof resultRuns.value)[number]) {
resultRunRenameId.value = run.id;
resultRunRenameTitle.value = run.title || t("tabs.runN", { n: run.sequence });
resultRunRenameTitle.value = run.title || run.sourceLabel || t("tabs.runN", { n: run.sequence });
resultRunRenameOpen.value = true;
nextTick(() => {
const input = document.querySelector<HTMLInputElement>("[data-result-run-name-input]");
@@ -1314,6 +1323,16 @@ async function selectResultRun(runId: string) {
return true;
}
/**
* 点击结果标签:切换显示的同时固定该结果(issue #9975)。
* 未固定的结果会被下一次普通查询复用/覆盖,点击后固定即可保留下来。
*/
async function selectResultRunFromTab(runId: string) {
if (!(await selectResultRun(runId))) return;
const run = resultRuns.value.find((item) => item.id === runId);
if (run && !run.pinned) toggleResultRunPinned(runId);
}
async function focusResultRunByIndex(index: number) {
const run = resultRuns.value[index];
if (!run) return;
@@ -1743,11 +1762,11 @@ defineExpose({
:aria-selected="run.active"
:data-active-result-run="run.active ? 'true' : undefined"
class="flex h-full select-none items-center gap-1 whitespace-nowrap pl-2.5 pr-1 text-xs font-medium outline-none focus-visible:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/50"
@click="selectResultRun(run.id)"
@click="selectResultRunFromTab(run.id)"
@keydown="onResultRunTabKeydown($event, runIndex)"
>
<Pin v-if="run.pinned" class="h-3 w-3 shrink-0 fill-current text-primary" />
{{ run.title || t("tabs.runN", { n: run.sequence }) }}
{{ run.title || run.sourceLabel || t("tabs.runN", { n: run.sequence }) }}
</button>
<button
type="button"
@@ -1767,17 +1786,17 @@ defineExpose({
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="ghost" size="sm" class="h-6 max-w-48 gap-1 px-2 text-xs">
<span class="min-w-0 truncate">{{ activeResultRunItem ? activeResultRunItem.title || t("tabs.runN", { n: activeResultRunItem.sequence }) : t("tabs.resultRuns") }}</span>
<span class="min-w-0 truncate">{{ activeResultRunItem ? activeResultRunItem.title || activeResultRunItem.sourceLabel || t("tabs.runN", { n: activeResultRunItem.sequence }) : t("tabs.resultRuns") }}</span>
<ChevronDown class="h-3.5 w-3.5 shrink-0" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" class="w-48">
<CustomContextMenu v-for="run in resultRuns" :key="run.id" :items="() => resultRunContextMenuItems(run)" v-slot="{ onContextMenu }">
<DropdownMenuItem class="flex items-center gap-2 pr-1" @select="selectResultRun(run.id)" @contextmenu="onContextMenu">
<DropdownMenuItem class="flex items-center gap-2 pr-1" @select="selectResultRunFromTab(run.id)" @contextmenu="onContextMenu">
<Check v-if="run.active" class="h-3.5 w-3.5 shrink-0" />
<span v-else class="h-3.5 w-3.5 shrink-0" />
<Pin v-if="run.pinned" class="h-3 w-3 shrink-0 fill-current text-primary" />
<span class="min-w-0 flex-1 truncate">{{ run.title || t("tabs.runN", { n: run.sequence }) }}</span>
<span class="min-w-0 flex-1 truncate">{{ run.title || run.sourceLabel || t("tabs.runN", { n: run.sequence }) }}</span>
<button
type="button"
class="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground"