From 829f6dc7eb88ca7b712153e46d18ba870a9ae090 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Wed, 24 Jun 2026 15:39:40 +0800 Subject: [PATCH] feat(grid): add current page sorting --- apps/desktop/src/components/grid/DataGrid.vue | 76 ++++++++++++----- .../src/components/layout/ContentArea.vue | 9 +- .../src/components/sidebar/TreeItem.vue | 2 + .../src/composables/useDataGridActions.ts | 13 ++- apps/desktop/src/i18n/locales/en.ts | 4 + apps/desktop/src/i18n/locales/es.ts | 4 + apps/desktop/src/i18n/locales/it.ts | 4 + apps/desktop/src/i18n/locales/ja.ts | 4 + apps/desktop/src/i18n/locales/pt-BR.ts | 4 + apps/desktop/src/i18n/locales/zh-CN.ts | 4 + apps/desktop/src/i18n/locales/zh-TW.ts | 4 + apps/desktop/src/lib/dataGridSort.ts | 66 +++++++++++++++ apps/desktop/src/lib/openTabsPersistence.ts | 2 + apps/desktop/src/stores/queryStore.ts | 49 +++++++++++ apps/desktop/src/types/database.ts | 4 + packages/app-tests/dataGridSort.test.ts | 45 ++++++++++ packages/app-tests/queryStore.test.ts | 82 +++++++++++++++---- 17 files changed, 337 insertions(+), 39 deletions(-) create mode 100644 packages/app-tests/dataGridSort.test.ts diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index bc08c6c37..ad4a6c282 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -60,6 +60,7 @@ import { PanelBottom, PanelRight, TableProperties, + Database, } from "@lucide/vue"; import { Button } from "@/components/ui/button"; import QueryLoadingState from "@/components/common/QueryLoadingState.vue"; @@ -139,7 +140,7 @@ import { useCellDetailEditor, type UseCellDetailEditorReturn } from "@/composabl import { useTheme } from "@/composables/useTheme"; import { useConnectionStore } from "@/stores/connectionStore"; import { useSettingsStore } from "@/stores/settingsStore"; -import type { DataGridSortDirection } from "@/lib/dataGridSort"; +import type { DataGridSortDirection, DataGridSortMode } from "@/lib/dataGridSort"; import { getTableMetadataCapabilities } from "@/lib/tableMetadataCapabilities"; import { forgetDataGridConditionHistory, loadDataGridConditionHistory, rememberDataGridConditionHistory } from "@/lib/dataGridConditionHistory"; import { caretPositionInsideInsertedSqlSingleQuotes, insertedSqlSingleQuoteAtCaret } from "@/lib/sqlQuoteCaret"; @@ -173,7 +174,7 @@ type ConditionSuggestion = { kind: "column" | "history"; }; -type SortMenuValue = "asc" | "desc" | "clear"; +type SortMenuValue = "local-asc" | "local-desc" | "database-asc" | "database-desc" | "clear"; const props = defineProps<{ result: QueryResult; @@ -190,6 +191,7 @@ const props = defineProps<{ sortColumn?: string; sortColumnIndex?: number; sortDirection?: DataGridSortDirection; + sortMode?: DataGridSortMode; tableMeta?: { schema?: string; tableName: string; @@ -217,7 +219,7 @@ const dataGridElapsed = () => `${Math.round(performance.now() - dataGridCreatedA const emit = defineEmits<{ reload: [sql?: string, searchText?: string, whereInput?: string, orderBy?: string, limit?: number, offset?: number]; paginate: [offset: number, limit: number, whereInput?: string, orderBy?: string]; - sort: [column: string, columnIndex: number, direction: "asc" | "desc" | null, whereInput?: string]; + sort: [column: string, columnIndex: number, direction: "asc" | "desc" | null, whereInput?: string, mode?: DataGridSortMode]; "update:whereInput": [value: string]; "update:orderByInput": [value: string]; }>(); @@ -354,16 +356,29 @@ function columnIsSorted(column: string, columnIndex: number): boolean { function sortMenuItems(column: string, columnIndex: number) { return [ { - label: t("grid.sortAscending"), - value: "asc", + label: t("grid.sortCurrentPageAscending"), + value: "local-asc", icon: ArrowUp, - checked: columnIsSorted(column, columnIndex) && sortDir.value === "asc", + checked: columnIsSorted(column, columnIndex) && sortDir.value === "asc" && sortMode.value === "local", }, { - label: t("grid.sortDescending"), - value: "desc", + label: t("grid.sortCurrentPageDescending"), + value: "local-desc", icon: ArrowDown, - checked: columnIsSorted(column, columnIndex) && sortDir.value === "desc", + checked: columnIsSorted(column, columnIndex) && sortDir.value === "desc" && sortMode.value === "local", + }, + { + label: t("grid.sortDatabaseAscending"), + value: "database-asc", + icon: Database, + checked: columnIsSorted(column, columnIndex) && sortDir.value === "asc" && sortMode.value === "database", + separatorBefore: true, + }, + { + label: t("grid.sortDatabaseDescending"), + value: "database-desc", + icon: Database, + checked: columnIsSorted(column, columnIndex) && sortDir.value === "desc" && sortMode.value === "database", }, { label: t("grid.clearSort"), @@ -376,7 +391,7 @@ function sortMenuItems(column: string, columnIndex: number) { } function selectedSortMenuValue(column: string, columnIndex: number): SortMenuValue | undefined { - return columnIsSorted(column, columnIndex) ? sortDir.value : undefined; + return columnIsSorted(column, columnIndex) ? (`${sortMode.value}-${sortDir.value}` as SortMenuValue) : undefined; } function typeColorClass(t: string): string { @@ -429,6 +444,7 @@ const transposeViewportWidth = ref(0); const sortCol = ref(null); const sortColIndex = ref(null); const sortDir = ref("asc"); +const sortMode = ref("database"); const searchText = ref(""); const deferredClientSearchText = ref(""); const searchOverlayVisible = ref(false); @@ -2454,14 +2470,15 @@ function syncOrderByInputWithSort(column: string | null, direction: "asc" | "des } watch( - () => [props.sortColumn, props.sortColumnIndex, props.sortDirection] as const, - ([column, columnIndex, direction], previous) => { + () => [props.sortColumn, props.sortColumnIndex, props.sortDirection, props.sortMode] as const, + ([column, columnIndex, direction, mode], previous) => { const wasControlledSort = !!previous?.[0] && !!previous?.[2]; const isControlledSort = !!column && !!direction; sortCol.value = column && direction ? column : null; sortColIndex.value = typeof columnIndex === "number" && direction ? columnIndex : null; sortDir.value = direction ?? "asc"; - if (isControlledSort) { + sortMode.value = mode ?? "database"; + if (isControlledSort && sortMode.value === "database") { syncOrderByInputWithSort(sortCol.value, sortDir.value); } else if (wasControlledSort) { syncOrderByInputWithSort(null, null); @@ -3740,7 +3757,7 @@ function setDetailNull() { detailCell.value = { ...detailCell.value! }; } -function applyColumnSort(column: string, columnIndex: number, direction: "asc" | "desc" | null) { +function applyColumnSort(column: string, columnIndex: number, direction: "asc" | "desc" | null, mode: DataGridSortMode = "database") { if (getIsResizing()) return; currentPage.value = 1; resetGridVerticalScroll(true); @@ -3748,23 +3765,34 @@ function applyColumnSort(column: string, columnIndex: number, direction: "asc" | sortCol.value = column; sortColIndex.value = columnIndex; sortDir.value = direction; - syncOrderByInputWithSort(column, direction); + sortMode.value = mode; + if (mode === "database") { + syncOrderByInputWithSort(column, direction); + } else { + syncOrderByInputWithSort(null, null); + } } else { sortCol.value = null; sortColIndex.value = null; sortDir.value = "asc"; + sortMode.value = "database"; syncOrderByInputWithSort(null, null); } - emit("sort", column, columnIndex, direction, currentWhereInput()); + emit("sort", column, columnIndex, direction, currentWhereInput(), mode); } function selectHeaderSort(value: string, column: string, columnIndex: number) { - applyColumnSort(column, columnIndex, value === "clear" ? null : (value as DataGridSortDirection)); + if (value === "clear") { + applyColumnSort(column, columnIndex, null, sortMode.value); + return; + } + const [mode, direction] = value.split("-") as [DataGridSortMode, DataGridSortDirection]; + applyColumnSort(column, columnIndex, direction, mode); } -function applyContextSort(direction: "asc" | "desc" | null) { +function applyContextSort(direction: "asc" | "desc" | null, mode: DataGridSortMode = "database") { if (!contextColumn.value || !contextCell.value) return; - applyColumnSort(contextColumn.value, contextCell.value.col, direction); + applyColumnSort(contextColumn.value, contextCell.value.col, direction, mode); } async function contextFilterCondition(mode: FilterMode): Promise { @@ -6366,9 +6394,15 @@ const gridContextMenuItems = computed(() => { // 2. Column sort & filter if (contextColumn.value) { - items.push({ label: t("grid.sortAscending"), action: () => applyContextSort("asc"), icon: ArrowUp }, { label: t("grid.sortDescending"), action: () => applyContextSort("desc"), icon: ArrowDown }); + items.push( + { label: t("grid.sortCurrentPageAscending"), action: () => applyContextSort("asc", "local"), icon: ArrowUp }, + { label: t("grid.sortCurrentPageDescending"), action: () => applyContextSort("desc", "local"), icon: ArrowDown }, + { label: "", separator: true }, + { label: t("grid.sortDatabaseAscending"), action: () => applyContextSort("asc", "database"), icon: Database }, + { label: t("grid.sortDatabaseDescending"), action: () => applyContextSort("desc", "database"), icon: Database }, + ); if (sortCol.value) { - items.push({ label: t("grid.clearSort"), action: () => applyContextSort(null), icon: ArrowUpDown }); + items.push({ label: t("grid.clearSort"), action: () => applyContextSort(null, sortMode.value), icon: ArrowUpDown }); } if (canUseWhereSearch.value) { items.push({ label: "", separator: true }); diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 010a7f68f..fa652e4f8 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -58,6 +58,7 @@ import { formatShortcut } from "@/lib/shortcutRegistry"; import { effectiveDatabaseTypeForConnection } from "@/lib/jdbcDialect"; import { chartableColumnIndexes } from "@/lib/chartData"; import type { SqlExecutionOverride } from "@/lib/sqlExecutionTarget"; +import type { DataGridSortMode } from "@/lib/dataGridSort"; import { useTabScroll } from "@/composables/useTabScroll"; import type { QueryTab, ConnectionConfig, TableInfoTab } from "@/types/database"; import type { SqlFormatDialect } from "@/lib/sqlFormatter"; @@ -119,7 +120,7 @@ const emit = defineEmits<{ formatError: []; reload: [sql?: string, searchText?: string, whereInput?: string, orderBy?: string, limit?: number, offset?: number]; paginate: [offset: number, limit: number, whereInput?: string, orderBy?: string]; - sort: [column: string, columnIndex: number, direction: "asc" | "desc" | null, whereInput?: string]; + sort: [column: string, columnIndex: number, direction: "asc" | "desc" | null, whereInput?: string, mode?: DataGridSortMode]; executeSql: [sql: string]; clickTable: [tableName: string]; viewTableData: [tableName: string]; @@ -764,6 +765,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe :sort-column="activeTab.resultSortColumn" :sort-column-index="activeTab.resultSortColumnIndex" :sort-direction="activeTab.resultSortDirection" + :sort-mode="activeTab.resultSortMode" :initial-order-by-input="activeTab.orderByInput" :sql="activeTab.lastExecutedSql || activeTab.sql" :loading="activeTab.isExecuting" @@ -787,7 +789,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe @update:order-by-input="(v: string) => (activeTab.orderByInput = v)" @reload="(sql?: string, searchText?: string, whereInput?: string, orderBy?: string, limit?: number, offset?: number) => emit('reload', sql, searchText, whereInput, orderBy, limit, offset)" @paginate="(offset: number, limit: number, whereInput?: string, orderBy?: string) => emit('paginate', offset, limit, whereInput, orderBy)" - @sort="(column: string, columnIndex: number, direction: 'asc' | 'desc' | null, whereInput?: string) => emit('sort', column, columnIndex, direction, whereInput)" + @sort="(column: string, columnIndex: number, direction: 'asc' | 'desc' | null, whereInput?: string, mode?: DataGridSortMode) => emit('sort', column, columnIndex, direction, whereInput, mode)" >