feat(editor): auto-transpose single-row query results

This commit is contained in:
二丫讲梵 2026-07-24 10:02:45 +08:00 committed by GitHub
parent b121240b2f
commit 0ad8e4a326
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 73 additions and 1 deletions

View File

@ -311,6 +311,7 @@ const editShowColumnCommentsInHeader = ref(settingsStore.editorSettings.showColu
const editShowColumnTypesInHeader = ref(settingsStore.editorSettings.showColumnTypesInHeader);
const editCompactColumnHeaderActions = ref(settingsStore.editorSettings.compactColumnHeaderActions);
const editDataGridQuickEntry = ref(settingsStore.editorSettings.dataGridQuickEntry);
const editDataGridAutoTransposeSingleRow = ref(settingsStore.editorSettings.dataGridAutoTransposeSingleRow);
const editTableOpenPageSize = ref(settingsStore.editorSettings.tableOpenPageSize);
const editInfiniteScroll = ref(settingsStore.editorSettings.infiniteScroll);
const editInfiniteScrollMaxRows = ref(settingsStore.editorSettings.infiniteScrollMaxRows);
@ -450,6 +451,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
showColumnTypesInHeader: editShowColumnTypesInHeader.value,
compactColumnHeaderActions: editCompactColumnHeaderActions.value,
dataGridQuickEntry: editDataGridQuickEntry.value,
dataGridAutoTransposeSingleRow: editDataGridAutoTransposeSingleRow.value,
tableOpenPageSize: editTableOpenPageSize.value,
infiniteScroll: editInfiniteScroll.value,
infiniteScrollMaxRows: editInfiniteScrollMaxRows.value,
@ -701,6 +703,7 @@ function syncEditorSettingsDraftFromStore() {
editShowColumnTypesInHeader.value = settingsStore.editorSettings.showColumnTypesInHeader;
editCompactColumnHeaderActions.value = settingsStore.editorSettings.compactColumnHeaderActions;
editDataGridQuickEntry.value = settingsStore.editorSettings.dataGridQuickEntry;
editDataGridAutoTransposeSingleRow.value = settingsStore.editorSettings.dataGridAutoTransposeSingleRow;
editTableOpenPageSize.value = settingsStore.editorSettings.tableOpenPageSize;
editInfiniteScroll.value = settingsStore.editorSettings.infiniteScroll;
editInfiniteScrollMaxRows.value = settingsStore.editorSettings.infiniteScrollMaxRows;
@ -927,6 +930,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editShowColumnTypesInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnTypesInHeader;
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
editDataGridQuickEntry.value = DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry;
editDataGridAutoTransposeSingleRow.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoTransposeSingleRow;
editTableOpenPageSize.value = DEFAULT_EDITOR_SETTINGS.tableOpenPageSize;
editInfiniteScroll.value = DEFAULT_EDITOR_SETTINGS.infiniteScroll;
editInfiniteScrollMaxRows.value = DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows;
@ -986,6 +990,7 @@ function resetAllDefaults() {
editShowColumnTypesInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnTypesInHeader;
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
editDataGridQuickEntry.value = DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry;
editDataGridAutoTransposeSingleRow.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoTransposeSingleRow;
editTableOpenPageSize.value = DEFAULT_EDITOR_SETTINGS.tableOpenPageSize;
editInfiniteScroll.value = DEFAULT_EDITOR_SETTINGS.infiniteScroll;
editInfiniteScrollMaxRows.value = DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows;
@ -3873,6 +3878,17 @@ onUnmounted(cleanupPreviewEditor);
</div>
<Switch id="data-grid-quick-entry" v-model="editDataGridQuickEntry" />
</div>
<div class="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-auto-transpose-single-row">
{{ t("settings.dataGridAutoTransposeSingleRow") }}
</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.dataGridAutoTransposeSingleRowDescription") }}
</p>
</div>
<Switch id="data-grid-auto-transpose-single-row" v-model="editDataGridAutoTransposeSingleRow" />
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="infinite-scroll">

View File

@ -103,6 +103,7 @@ import {
nextTransposeState,
nextTransposeStateForRecordCount,
restoreDataGridAfterTranspose,
shouldAutoTransposeSingleRow,
transposeRecordIndexesForMode,
transposeRecordWidthsForDensity,
transposeFieldWidth,
@ -269,6 +270,7 @@ interface DataGridProps {
executionDatabase?: string;
schema?: string;
context?: "results" | "table-data";
autoTransposeSingleRow?: boolean;
sourceColumns?: Array<string | undefined>;
initialWhereInput?: string;
initialOrderByInput?: string;
@ -394,6 +396,18 @@ watch(
{ immediate: true },
);
watch(
() => props.result,
(result) => {
if (!shouldAutoTransposeSingleRow({ enabled: !!props.autoTransposeSingleRow, preserveTranspose: preserveTransposeOnNextResult.value, rowCount: result.rows.length, columnCount: result.columns.length })) return;
nextTick(() => {
if (props.result !== result || !props.autoTransposeSingleRow || result.rows.length !== 1 || result.columns.length <= 1) return;
applyTransposeState({ showTranspose: true, transposeRowIndex: 0 });
});
},
{ immediate: true },
);
const hasData = computed(() => props.result.columns.length > 0);
const columnTypeMap = computed(() => {

View File

@ -1171,6 +1171,7 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
:allow-insert-rows="activeTab.queryAnalysis?.allowInsert !== false && activeTab.queryAnalysis?.allowInsertDelete !== false"
:allow-delete-rows="activeTab.queryAnalysis?.allowInsertDelete !== false"
context="results"
:auto-transpose-single-row="settingsStore.editorSettings.dataGridAutoTransposeSingleRow"
:database-type="activeEffectiveDatabaseType"
:connection-id="activeTab.connectionId"
:database="activeTab.database"

View File

@ -3836,6 +3836,8 @@ export default {
compactColumnHeaderActionsDescription: "Move formatter and local filter tools into a more menu so column names get priority.",
dataGridQuickEntry: "Quick grid entry",
dataGridQuickEntryDescription: "When enabled, editing a cell or filling the bottom blank row saves to the database as soon as focus leaves. Useful for frequent entry, but it can cause accidental writes.",
dataGridAutoTransposeSingleRow: "Auto-transpose single-row query results",
dataGridAutoTransposeSingleRowDescription: "When enabled, SQL query results with exactly one row and multiple columns automatically switch to transpose view.",
infiniteScroll: "Infinite scroll loading",
autoCalculateTotalRows: "Auto-calculate total row count",
autoCalculateTotalRowsDescription: "Run COUNT(*) automatically after each query to show the total matching rows. Off by default to keep large queries fast — calculate it on demand from the result footer.",

View File

@ -3614,6 +3614,8 @@ export default withEnglishFallback({
compactColumnHeaderActionsDescription: "Mueve formatear y filtrar a un menú de más acciones para priorizar el nombre de columna.",
dataGridQuickEntry: "Entrada rápida en grilla",
dataGridQuickEntryDescription: "Al activarlo, editar una celda o llenar la fila vacía inferior se guarda en la base de datos al perder el foco. Agiliza la captura frecuente, pero puede causar escrituras accidentales.",
dataGridAutoTransposeSingleRow: "Transponer automáticamente los resultados de consulta de una sola fila",
dataGridAutoTransposeSingleRowDescription: "Al activarlo, los resultados de consultas SQL con exactamente una fila y varias columnas cambian automáticamente a la vista transpuesta.",
infiniteScroll: "Carga de desplazamiento infinito",
autoCalculateTotalRows: "Calcular automáticamente el total de filas",
autoCalculateTotalRowsDescription: "Ejecuta COUNT(*) automáticamente tras cada consulta para mostrar el total de filas coincidentes. Desactivado por defecto para mantener rápidas las consultas grandes; puedes calcularlo cuando quieras desde el pie de resultados.",

View File

@ -3612,6 +3612,8 @@ export default withEnglishFallback({
compactColumnHeaderActionsDescription: "Sposta gli strumenti del formattatore e dei filtri locali in un menu 'altro' in modo che i nomi delle colonne abbiano la priorità.",
dataGridQuickEntry: "Inserimento rapido griglia",
dataGridQuickEntryDescription: "Se attivo, la modifica di una cella o la compilazione della riga vuota in fondo viene salvata nel database appena il focus esce. Utile per inserimenti frequenti, ma può causare scritture accidentali.",
dataGridAutoTransposeSingleRow: "Trasponi automaticamente i risultati delle query a riga singola",
dataGridAutoTransposeSingleRowDescription: "Quando è attivata, i risultati delle query SQL con esattamente una riga e più colonne passano automaticamente alla vista trasposta.",
infiniteScroll: "Caricamento a scorrimento infinito",
autoCalculateTotalRows: "Calcola automaticamente il totale delle righe",
autoCalculateTotalRowsDescription: "Esegue COUNT(*) automaticamente dopo ogni query per mostrare il totale delle righe corrispondenti. Disattivato per impostazione predefinita per mantenere veloci le query grandi; puoi calcolarlo all'occorrenza dal piè di pagina dei risultati.",

View File

@ -3609,6 +3609,8 @@ export default withEnglishFallback({
compactColumnHeaderActionsDescription: "フォーマッターとローカルフィルターツールをもっと見るメニューに移動し、列名を優先表示します。",
dataGridQuickEntry: "グリッド高速入力",
dataGridQuickEntryDescription: "有効にすると、セル編集や下部の空行入力はフォーカスが外れた時点でデータベースに保存されます。頻繁な入力に便利ですが、誤操作による書き込みのリスクがあります。",
dataGridAutoTransposeSingleRow: "1 行のクエリ結果を自動で転置表示",
dataGridAutoTransposeSingleRowDescription: "有効にすると、1 行かつ複数列の SQL クエリ結果が自動的に転置表示に切り替わります。",
tableColumnTemplateFields: "新規テーブルのプリセット列",
tableColumnTemplateFieldsDescription: "データベース種別を選択し、新規テーブル作成時に使うプリセット列の型を設定します。",
tableColumnTemplateAdd: "列を追加",

View File

@ -3614,6 +3614,8 @@ export default withEnglishFallback({
compactColumnHeaderActionsDescription: "Mover as ferramentas de formatação e filtro local para um menu de mais opções para que os nomes das colunas tenham prioridade.",
dataGridQuickEntry: "Entrada rápida na grade",
dataGridQuickEntryDescription: "Quando ativado, editar uma célula ou preencher a linha vazia inferior salva no banco de dados assim que o foco sai. Ajuda na entrada frequente, mas pode causar gravações acidentais.",
dataGridAutoTransposeSingleRow: "Transpor automaticamente resultados de consulta com uma única linha",
dataGridAutoTransposeSingleRowDescription: "Quando ativado, os resultados de consultas SQL com exatamente uma linha e várias colunas alternam automaticamente para a visualização transposta.",
infiniteScroll: "Carregamento por rolagem infinita",
autoCalculateTotalRows: "Calcular automaticamente o total de linhas",
autoCalculateTotalRowsDescription: "Executa COUNT(*) automaticamente após cada consulta para mostrar o total de linhas correspondentes. Desativado por padrão para manter consultas grandes rápidas; você pode calculá-lo quando quiser no rodapé dos resultados.",

View File

@ -3825,6 +3825,8 @@ export default withEnglishFallback({
compactColumnHeaderActionsDescription: "将格式化、本地筛选收进更多菜单,优先显示字段名称。",
dataGridQuickEntry: "表格快捷录入",
dataGridQuickEntryDescription: "开启后,编辑单元格或填写底部空行并离焦会立即保存到数据库。便于高频录入,但存在误操作风险。",
dataGridAutoTransposeSingleRow: "自动转置单行查询结果",
dataGridAutoTransposeSingleRowDescription: "开启后SQL 查询结果只有一行且包含多列时,自动切换为转置视图。",
infiniteScroll: "无限滚动加载",
autoCalculateTotalRows: "自动统计总行数",
autoCalculateTotalRowsDescription: "每次查询后自动执行 COUNT(*) 显示匹配的总行数。默认关闭以保证大查询速度 —— 可在结果栏按需手动统计。",

View File

@ -3425,6 +3425,8 @@ export default withEnglishFallback({
compactColumnHeaderActionsDescription: "將格式設定和本機篩選工具移到更多選單,讓欄位名稱優先顯示。",
dataGridQuickEntry: "表格快速輸入",
dataGridQuickEntryDescription: "開啟後,編輯儲存格或填寫底部空白列並離焦會立即儲存到資料庫。適合高頻輸入,但有誤操作風險。",
dataGridAutoTransposeSingleRow: "自動轉置單行查詢結果",
dataGridAutoTransposeSingleRowDescription: "開啟後SQL 查詢結果只有一行且包含多個欄位時,會自動切換為轉置檢視。",
infiniteScroll: "無限滾動載入",
autoCalculateTotalRows: "自動統計總筆數",
autoCalculateTotalRowsDescription: "每次查詢後自動執行 COUNT(*) 顯示符合的總筆數。預設關閉以確保大型查詢速度 —— 可在結果列按需手動統計。",

View File

@ -1,5 +1,16 @@
import { describe, expect, it } from "vitest";
import { averageTransposeRecordWidth, calculateTransposeRecordWidth, defaultTransposeRecordWidth, minTransposeFieldWidth, transposeFieldWidth, transposeRecordWidthsForDensity, visibleTransposeRecordWindow } from "@/lib/dataGrid/dataGridTranspose";
import { averageTransposeRecordWidth, calculateTransposeRecordWidth, defaultTransposeRecordWidth, minTransposeFieldWidth, shouldAutoTransposeSingleRow, transposeFieldWidth, transposeRecordWidthsForDensity, visibleTransposeRecordWindow } from "@/lib/dataGrid/dataGridTranspose";
describe("single-row automatic transpose", () => {
it("only opens for enabled multi-column results that are not preserving a manual transpose", () => {
expect(shouldAutoTransposeSingleRow({ enabled: true, preserveTranspose: false, rowCount: 1, columnCount: 2 })).toBe(true);
expect(shouldAutoTransposeSingleRow({ enabled: false, preserveTranspose: false, rowCount: 1, columnCount: 2 })).toBe(false);
expect(shouldAutoTransposeSingleRow({ enabled: true, preserveTranspose: true, rowCount: 1, columnCount: 2 })).toBe(false);
expect(shouldAutoTransposeSingleRow({ enabled: true, preserveTranspose: false, rowCount: 0, columnCount: 2 })).toBe(false);
expect(shouldAutoTransposeSingleRow({ enabled: true, preserveTranspose: false, rowCount: 2, columnCount: 2 })).toBe(false);
expect(shouldAutoTransposeSingleRow({ enabled: true, preserveTranspose: false, rowCount: 1, columnCount: 1 })).toBe(false);
});
});
describe("dataGridTranspose density widths", () => {
it("uses the shared density preset for record and field widths", () => {

View File

@ -168,6 +168,13 @@ export interface TransposeRecordIndexesForModeOptions {
visibleRecordIndexes: number[];
}
export interface AutoTransposeSingleRowOptions {
enabled: boolean;
preserveTranspose: boolean;
rowCount: number;
columnCount: number;
}
export function nextTransposeState(showTranspose: boolean, transposeRowIndex: number | null, requestedRowIndex: number): DataGridTransposeState {
if (showTranspose && transposeRowIndex === requestedRowIndex) {
return { showTranspose: false, transposeRowIndex: null };
@ -209,6 +216,10 @@ export function nextTransposeStateForRecordCount(showTranspose: boolean, transpo
};
}
export function shouldAutoTransposeSingleRow(options: AutoTransposeSingleRowOptions): boolean {
return options.enabled && !options.preserveTranspose && options.rowCount === 1 && options.columnCount > 1;
}
export function buildTransposeRows<T>(options: BuildTransposeRowsOptions<T>): Array<DataGridTransposeRow<T>> {
return options.columns.map((column, columnIndex) => {
return {

View File

@ -28,6 +28,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"showColumnTypesInHeader",
"compactColumnHeaderActions",
"dataGridQuickEntry",
"dataGridAutoTransposeSingleRow",
"tableOpenPageSize",
"infiniteScroll",
"infiniteScrollMaxRows",

View File

@ -417,6 +417,7 @@ export interface EditorSettings {
dataGridQuickEntry: boolean;
dataGridRenderMode: DataGridRenderMode;
dataGridSearchMode: DataGridSearchMode;
dataGridAutoTransposeSingleRow: boolean;
dataGridMultiRowTranspose: boolean;
dataGridHideNullColumns: boolean;
tableFontFamily: string;
@ -579,6 +580,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
dataGridQuickEntry: false,
dataGridRenderMode: "canvas",
dataGridSearchMode: "filter",
dataGridAutoTransposeSingleRow: false,
dataGridMultiRowTranspose: false,
dataGridHideNullColumns: false,
tableFontFamily: DEFAULT_DATA_GRID_FONT_FAMILY,
@ -856,6 +858,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
dataGridQuickEntry: settings.dataGridQuickEntry ?? DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry,
dataGridRenderMode: normalizeDataGridRenderMode(settings.dataGridRenderMode),
dataGridSearchMode: normalizeDataGridSearchMode(settings.dataGridSearchMode),
dataGridAutoTransposeSingleRow: settings.dataGridAutoTransposeSingleRow === true,
dataGridMultiRowTranspose: settings.dataGridMultiRowTranspose === true,
dataGridHideNullColumns: settings.dataGridHideNullColumns === true,
tableFontFamily: normalizeFontFamily(settings.tableFontFamily, DEFAULT_EDITOR_SETTINGS.tableFontFamily),
@ -1226,6 +1229,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.dataGridQuickEntry !== undefined) editorSettings.value.dataGridQuickEntry = partial.dataGridQuickEntry;
if (partial.dataGridRenderMode !== undefined) editorSettings.value.dataGridRenderMode = normalizeDataGridRenderMode(partial.dataGridRenderMode);
if (partial.dataGridSearchMode !== undefined) editorSettings.value.dataGridSearchMode = normalizeDataGridSearchMode(partial.dataGridSearchMode);
if (partial.dataGridAutoTransposeSingleRow !== undefined) editorSettings.value.dataGridAutoTransposeSingleRow = partial.dataGridAutoTransposeSingleRow === true;
if (partial.dataGridMultiRowTranspose !== undefined) editorSettings.value.dataGridMultiRowTranspose = partial.dataGridMultiRowTranspose === true;
if (partial.dataGridHideNullColumns !== undefined) editorSettings.value.dataGridHideNullColumns = partial.dataGridHideNullColumns === true;
if (partial.tableFontFamily !== undefined) editorSettings.value.tableFontFamily = normalizeFontFamily(partial.tableFontFamily, DEFAULT_EDITOR_SETTINGS.tableFontFamily);