From 6517284d952f7cce8f6f5d42fea596f288e7d2b3 Mon Sep 17 00:00:00 2001 From: xianghu908 <76929699+xianghu908@users.noreply.github.com> Date: Thu, 24 Sep 2026 12:11:58 +0800 Subject: [PATCH] feat(data-grid): make the row number column show original row numbers --- apps/desktop/src/components/grid/DataGrid.vue | 12 +++- .../src/components/layout/ContentArea.vue | 60 ++++++++++++++++++- apps/desktop/src/i18n/locales/az.ts | 4 ++ 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/ko.ts | 4 ++ apps/desktop/src/i18n/locales/pt-BR.ts | 4 ++ apps/desktop/src/i18n/locales/tr.ts | 4 ++ apps/desktop/src/i18n/locales/zh-CN.ts | 4 ++ apps/desktop/src/i18n/locales/zh-TW.ts | 4 ++ .../__tests__/dataGridRowNumber.spec.ts | 31 ++++++++++ .../lib/dataGrid/canvasDataGridRenderer.ts | 22 ++++--- .../src/lib/dataGrid/dataGridRowNumber.ts | 25 ++++++++ .../stores/__tests__/settingsStore.spec.ts | 6 ++ apps/desktop/src/stores/settingsStore.ts | 10 ++++ 17 files changed, 196 insertions(+), 10 deletions(-) create mode 100644 apps/desktop/src/lib/dataGrid/__tests__/dataGridRowNumber.spec.ts create mode 100644 apps/desktop/src/lib/dataGrid/dataGridRowNumber.ts diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 3d71c07adf..3c7c48a9ca 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -231,6 +231,7 @@ import { } from "@/lib/dataGrid/dataGridInfiniteScroll"; import { resolveDataGridWheelScroll } from "@/lib/dataGrid/dataGridWheel"; import { CANVAS_DATA_GRID_ROW_HEIGHT, MAX_CANVAS_DATA_GRID_PIXEL_RATIO, canvasDataGridActionOverlayWidth, canvasDataGridActionReservedWidth, dataGridSearchMatchKey, drawCanvasDataGrid, resolveCanvasCellTextLayout, type CanvasDevicePixelSize } from "@/lib/dataGrid/canvasDataGridRenderer"; +import { resolveDataGridRowNumberLabel } from "@/lib/dataGrid/dataGridRowNumber"; import { resolveCrosshairTarget, type CrosshairTarget } from "@/lib/dataGrid/crosshairHighlight"; import { DATA_GRID_DARK_STRIPED_ROW_BG, DATA_GRID_LIGHT_STRIPED_ROW_BG, dataGridActiveRowBackground } from "@/lib/dataGrid/dataGridPaintTheme"; import { createRowLowerTextCache } from "@/lib/dataGrid/dataGridRowLowerText"; @@ -757,6 +758,7 @@ const columnIndexMap = computed(() => buildColumnIndexMap(indexes.value, primary const compactColumnHeaderActions = computed(() => settingsStore.editorSettings.compactColumnHeaderActions); const dataGridRenderMode = computed(() => settingsStore.editorSettings.dataGridRenderMode); const dataGridSearchMode = computed(() => settingsStore.editorSettings.dataGridSearchMode); +const dataGridRowNumberMode = computed(() => settingsStore.editorSettings.dataGridRowNumberMode); const compactDataGridToolbar = computed(() => dataGridTopbarOverflowCompact.value || isDataGridToolbarCompact(dataGridTopbarWidth.value, dataGridViewportWidth.value, DATA_GRID_CONDITION_TOOLBAR_MIN_WIDTH)); const splitDataGridToolbar = computed(() => settingsStore.editorSettings.dataGridToolbarLayout === "split"); const responsiveDataGridToolbarActionCount = computed(() => dataGridToolbarActionCollapseCount(dataGridTopbarWidth.value, dataGridViewportWidth.value, DATA_GRID_CONDITION_TOOLBAR_MIN_WIDTH)); @@ -6387,8 +6389,13 @@ function rowNumberPageOffset(): number { function rowNumberText(item: RowItem | undefined): string { if (!item) return ""; - if (item.isDraft) return "*"; - return String(item.displayIndex + 1 + rowNumberPageOffset()); + return resolveDataGridRowNumberLabel({ + displayIndex: item.displayIndex, + sourceIndex: item.sourceIndex, + isDraft: item.isDraft, + sourceRowNumbers: dataGridRowNumberMode.value === "source", + pageOffset: rowNumberPageOffset(), + }); } const quickEntryDraftPlaceholder = computed(() => t("grid.quickEntryDraftPlaceholder")); @@ -7286,6 +7293,7 @@ function drawCanvasGrid() { booleanDisplayMode: booleanDisplayMode.value, flatteningMultiLineEnabled: flatteningMultiLineEnabled.value, showWhitespace: showWhitespaceEnabled.value, + rowNumberMode: dataGridRowNumberMode.value, }); if (!drawn) return; flipCanvasSurface(); diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index d2716ce54c..2e234c2416 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -34,6 +34,7 @@ import { Pin, Pencil, Rows3, + Hash, SquareDashed, Minus, Plus, @@ -136,7 +137,7 @@ const QueryChart = defineAsyncComponent(() => import("@/components/chart/QueryCh import { useQueryStore } from "@/stores/queryStore"; import { useConnectionStore } from "@/stores/connectionStore"; import type { ContentAreaSurfaceEmits, ContentAreaSurfaceProps } from "@/components/layout/querySurfaces"; -import { TABLE_FONT_SIZE_MAX, TABLE_FONT_SIZE_MIN, useSettingsStore, type DataGridSearchMode, type ResultRunDisplayMode } from "@/stores/settingsStore"; +import { TABLE_FONT_SIZE_MAX, TABLE_FONT_SIZE_MIN, useSettingsStore, type DataGridRowNumberMode, type DataGridSearchMode, type ResultRunDisplayMode } from "@/stores/settingsStore"; import { useToast } from "@/composables/useToast"; import { isTauriRuntime } from "@/lib/backend/tauriRuntime"; import { canCancelQueryExecution, isActiveResultLoading, queryExecutionLabelKey } from "@/lib/sql/queryExecutionState"; @@ -328,6 +329,7 @@ const dataToolbarCompact = computed(() => dataToolbarTier.value >= 1); const showDataColumnsChip = computed(() => dataToolbarTier.value < 2); const dataGridRenderMode = computed(() => settingsStore.editorSettings.dataGridRenderMode); const dataGridSearchMode = computed(() => settingsStore.editorSettings.dataGridSearchMode); +const dataGridRowNumberMode = computed(() => settingsStore.editorSettings.dataGridRowNumberMode); const resultRunDisplayMode = computed(() => settingsStore.editorSettings.resultRunDisplayMode); const columnWidthDensity = computed(() => settingsStore.editorSettings.columnWidthDensity); const tableFontSize = computed(() => settingsStore.editorSettings.tableFontSize); @@ -398,6 +400,10 @@ function setDataGridSearchMode(value: DataGridSearchMode) { settingsStore.updateEditorSettings({ dataGridSearchMode: value }); } +function setDataGridRowNumberMode(value: DataGridRowNumberMode) { + settingsStore.updateEditorSettings({ dataGridRowNumberMode: value }); +} + function setResultRunDisplayMode(value: ResultRunDisplayMode) { settingsStore.updateEditorSettings({ resultRunDisplayMode: value }); } @@ -1821,6 +1827,32 @@ defineExpose({ +
+
+ + {{ t("grid.rowNumberMode") }} +
+ +
+ + +
+
+
@@ -2425,6 +2457,32 @@ defineExpose({
+
+
+ + {{ t("grid.rowNumberMode") }} +
+ +
+ + +
+
+
diff --git a/apps/desktop/src/i18n/locales/az.ts b/apps/desktop/src/i18n/locales/az.ts index 985583b035..6f9c444487 100644 --- a/apps/desktop/src/i18n/locales/az.ts +++ b/apps/desktop/src/i18n/locales/az.ts @@ -2063,6 +2063,10 @@ export default withEnglishFallback({ searchModeHint: "Ctrl+F düymələrinin uyğun sətirləri süzməsini, yoxsa bütün sətirləri göstərib uyğunluqları vurğulamasını seçin.", searchModeFilter: "Süzgəc", searchModeHighlight: "Vurğulama", + rowNumberMode: "Sətir nömrəsinin göstərilməsi", + rowNumberModeHint: "Sətir nömrəsi sütununun cari görünüş mövqeyini, yoxsa filtrdən əvvəlki orijinal sətir nömrəsini göstərəcəyini seçin. Orijinal sətir nömrələri sətirlər filtrləndikdə dəyişməz qalır.", + rowNumberModeView: "Görünüş mövqeyi", + rowNumberModeSource: "Orijinal nömrə", colorizeDataTypes: "Verilən növlərinin rəngləri", numericColumnAlign: "Ədədi sütunun düzləndirilməsi", numericColumnAlignLeft: "Sola", diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index ad76ec4c96..588e7992ee 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -2301,6 +2301,10 @@ export default { searchModeHint: "Choose whether Ctrl+F filters matching rows or keeps all rows visible and highlights matches.", searchModeFilter: "Filter", searchModeHighlight: "Highlight", + rowNumberMode: "Row number display", + rowNumberModeHint: "Choose whether the row number column shows the current view position or the original row number before filtering. Original row numbers stay stable when rows are filtered out.", + rowNumberModeView: "View position", + rowNumberModeSource: "Original number", colorizeDataTypes: "Data type colors", numericColumnAlign: "Numeric Column Align", numericColumnAlignLeft: "Left", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index a6434c0a31..7265775667 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -2550,6 +2550,10 @@ export default withEnglishFallback({ }, cachedResultUnavailable: "Resultado en caché faltante o incompatible.", reexecuteQuery: "Volver a ejecutar consulta", + rowNumberMode: "Visualización del número de fila", + rowNumberModeHint: "Elige si la columna de números de fila muestra la posición actual en la vista o el número de fila original antes de filtrar. Los números de fila originales no cambian al filtrar filas.", + rowNumberModeView: "Posición en la vista", + rowNumberModeSource: "Número original", colorizeDataTypes: "Colores de tipos de datos", numericColumnAlign: "Alineación de columna numérica", numericColumnAlignLeft: "Alineación izquierda", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index 0ea54d8fc0..e06cd1e713 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -2547,6 +2547,10 @@ export default withEnglishFallback({ }, cachedResultUnavailable: "Risultato in cache mancante o non compatibile.", reexecuteQuery: "Esegui di nuovo la query", + rowNumberMode: "Visualizzazione numero di riga", + rowNumberModeHint: "Scegli se la colonna dei numeri di riga mostra la posizione corrente nella vista o il numero di riga originale prima del filtro. I numeri di riga originali restano invariati quando le righe vengono filtrate.", + rowNumberModeView: "Posizione nella vista", + rowNumberModeSource: "Numero originale", colorizeDataTypes: "Colori dei tipi di dato", numericColumnAlign: "Allineamento colonna numerica", numericColumnAlignLeft: "Allineamento a sinistra", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index be91a66376..cd1f1d676f 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -2601,6 +2601,10 @@ export default withEnglishFallback({ searchModeHint: "Ctrl+F 検索時に一致する行をフィルタリングするか、全行を表示したまま一致箇所をハイライトするかを選択します。", searchModeFilter: "フィルター", searchModeHighlight: "ハイライト", + rowNumberMode: "行番号の表示", + rowNumberModeHint: "行番号欄に現在の表示位置とフィルター前の元の行番号のどちらを表示するか選択します。元の行番号は絞り込み後も変わりません。", + rowNumberModeView: "表示位置", + rowNumberModeSource: "元の行番号", emptyStringValue: "空文字", jumpToPage: "ページ番号に移動", importBinaryValue: "ファイルからインポート", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index e4483feebc..4c2531b9c0 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -2140,6 +2140,10 @@ export default withEnglishFallback({ searchModeHint: "Ctrl+F가 일치하는 행을 필터링할지, 모든 행을 표시하고 일치 항목을 강조할지 선택하세요.", searchModeFilter: "필터", searchModeHighlight: "강조", + rowNumberMode: "행 번호 표시", + rowNumberModeHint: "행 번호 열에 현재 보기 위치와 필터링 전 원래 행 번호 중 무엇을 표시할지 선택하세요. 원래 행 번호는 필터링 후에도 바뀌지 않습니다.", + rowNumberModeView: "보기 위치", + rowNumberModeSource: "원래 행 번호", colorizeDataTypes: "데이터 타입 색상", numericColumnAlign: "숫자 컬럼 정렬", numericColumnAlignLeft: "왼쪽", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 27a382fd8f..2c0146ba5a 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -2549,6 +2549,10 @@ export default withEnglishFallback({ }, cachedResultUnavailable: "Resultado em cache ausente ou incompatível.", reexecuteQuery: "Reexecutar consulta", + rowNumberMode: "Exibição do número da linha", + rowNumberModeHint: "Escolha se a coluna de números de linha mostra a posição atual na visualização ou o número de linha original antes da filtragem. Os números de linha originais permanecem inalterados quando linhas são filtradas.", + rowNumberModeView: "Posição na visualização", + rowNumberModeSource: "Número original", colorizeDataTypes: "Cores por tipo de dado", numericColumnAlign: "Alinhamento de colunas numéricas", numericColumnAlignLeft: "Alinhamento à esquerda", diff --git a/apps/desktop/src/i18n/locales/tr.ts b/apps/desktop/src/i18n/locales/tr.ts index c191c161d9..cabbea2bbb 100644 --- a/apps/desktop/src/i18n/locales/tr.ts +++ b/apps/desktop/src/i18n/locales/tr.ts @@ -2058,6 +2058,10 @@ export default withEnglishFallback({ searchModeHint: "Ctrl+F'nin eşleşen satırları filtrelemesini mi yoksa tüm satırları görünür tutup eşleşmeleri vurgulamasını mı istediğinizi seçin.", searchModeFilter: "Filtre", searchModeHighlight: "Vurgula", + rowNumberMode: "Satır numarası gösterimi", + rowNumberModeHint: "Satır numarası sütununda geçerli görünüm konumunun mu yoksa filtrelemeden önceki özgün satır numarasının mı gösterileceğini seçin. Özgün satır numaraları filtrelemeden sonra değişmez.", + rowNumberModeView: "Görünüm konumu", + rowNumberModeSource: "Özgün numara", colorizeDataTypes: "Veri türü renkleri", numericColumnAlign: "Sayısal Sütun Hizalaması", numericColumnAlignLeft: "Sol", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 9d108a5cc3..3f49415086 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -2214,6 +2214,10 @@ export default withEnglishFallback({ searchModeHint: "选择 Ctrl+F 搜索时过滤匹配行,或保留全部行并高亮定位命中项。", searchModeFilter: "过滤", searchModeHighlight: "高亮", + rowNumberMode: "行号显示方式", + rowNumberModeHint: "选择行号栏显示当前视图序号,或筛选前的原始行号;原始行号在筛选、搜索后保持不变,便于定位数据在完整结果中的位置。", + rowNumberModeView: "视图序号", + rowNumberModeSource: "原始行号", colorizeDataTypes: "数据类型着色", numericColumnAlign: "数值列对齐", numericColumnAlignLeft: "左对齐", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index d9bbdf92d2..18f07423f4 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -2552,6 +2552,10 @@ export default withEnglishFallback({ }, cachedResultUnavailable: "快取結果缺失或不相容。", reexecuteQuery: "重新執行查詢", + rowNumberMode: "行號顯示方式", + rowNumberModeHint: "選擇行號欄顯示目前檢視序號,或篩選前的原始行號;原始行號在篩選、搜尋後保持不變,便於定位資料在完整結果中的位置。", + rowNumberModeView: "檢視序號", + rowNumberModeSource: "原始行號", colorizeDataTypes: "資料類型著色", numericColumnAlign: "數值列對齊", numericColumnAlignLeft: "左對齊", diff --git a/apps/desktop/src/lib/dataGrid/__tests__/dataGridRowNumber.spec.ts b/apps/desktop/src/lib/dataGrid/__tests__/dataGridRowNumber.spec.ts new file mode 100644 index 0000000000..3111d12c2e --- /dev/null +++ b/apps/desktop/src/lib/dataGrid/__tests__/dataGridRowNumber.spec.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from "vitest"; +import { resolveDataGridRowNumberLabel } from "@/lib/dataGrid/dataGridRowNumber"; + +describe("data grid row number labels", () => { + it("keeps the current view position by default", () => { + expect(resolveDataGridRowNumberLabel({ displayIndex: 0, sourceRowNumbers: false, pageOffset: 0 })).toBe("1"); + expect(resolveDataGridRowNumberLabel({ displayIndex: 41, sourceIndex: 137, sourceRowNumbers: false, pageOffset: 0 })).toBe("42"); + }); + + it("adds the page offset in paged results", () => { + expect(resolveDataGridRowNumberLabel({ displayIndex: 0, sourceRowNumbers: false, pageOffset: 100 })).toBe("101"); + expect(resolveDataGridRowNumberLabel({ displayIndex: 0, sourceIndex: 0, sourceRowNumbers: true, pageOffset: 100 })).toBe("101"); + }); + + it("survives filtering by reporting the original row number", () => { + // 第 137 行被筛成视图里的第 3 行:原始行号模式下仍应显示 137 + expect(resolveDataGridRowNumberLabel({ displayIndex: 2, sourceIndex: 136, sourceRowNumbers: true, pageOffset: 0 })).toBe("137"); + // 分页场景下 sourceIndex 是页内下标,需要叠加页偏移 + expect(resolveDataGridRowNumberLabel({ displayIndex: 2, sourceIndex: 36, sourceRowNumbers: true, pageOffset: 100 })).toBe("137"); + }); + + it("falls back to the view position for rows without a source index", () => { + // 新增行还没有落库位置,sourceIndex 为 undefined + expect(resolveDataGridRowNumberLabel({ displayIndex: 7, sourceRowNumbers: true, pageOffset: 0 })).toBe("8"); + }); + + it("keeps the draft placeholder regardless of the mode", () => { + expect(resolveDataGridRowNumberLabel({ displayIndex: 7, isDraft: true, sourceRowNumbers: false, pageOffset: 0 })).toBe("*"); + expect(resolveDataGridRowNumberLabel({ displayIndex: 7, sourceIndex: 3, isDraft: true, sourceRowNumbers: true, pageOffset: 100 })).toBe("*"); + }); +}); diff --git a/apps/desktop/src/lib/dataGrid/canvasDataGridRenderer.ts b/apps/desktop/src/lib/dataGrid/canvasDataGridRenderer.ts index 717466d54b..5ccea1a845 100644 --- a/apps/desktop/src/lib/dataGrid/canvasDataGridRenderer.ts +++ b/apps/desktop/src/lib/dataGrid/canvasDataGridRenderer.ts @@ -7,6 +7,7 @@ import type { CellSelectionRange } from "@/lib/dataGrid/gridSelection"; import type { RowStatus } from "@/lib/dataGrid/gridRowStatus"; import { DATA_GRID_DARK_SEARCH_COLORS, dataGridTypeForeground, resolveDataGridPaintTheme, type DataGridPaintTheme } from "@/lib/dataGrid/dataGridPaintTheme"; import type { CrosshairTarget } from "@/lib/dataGrid/crosshairHighlight"; +import { resolveDataGridRowNumberLabel } from "@/lib/dataGrid/dataGridRowNumber"; export const CANVAS_DATA_GRID_ROW_HEIGHT = 26; export const MAX_CANVAS_DATA_GRID_PIXEL_RATIO = 4; @@ -102,6 +103,8 @@ export interface DrawCanvasDataGridOptions { booleanDisplayMode?: "checkbox" | "dropdown"; flatteningMultiLineEnabled: boolean; showWhitespace?: boolean; + /** 行号栏取值:`view` = 当前视图序号(默认,与筛选前一致),`source` = 筛选前的原始行号 */ + rowNumberMode?: "view" | "source"; } type NumericCanvasContext = CanvasRenderingContext2D & { @@ -365,6 +368,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions): boolean booleanDisplayMode = "dropdown", flatteningMultiLineEnabled, showWhitespace = false, + rowNumberMode = "view", } = options; // 框选热路径:整次绘制只判断一次。常见情况(单矩形 / 多列且每段都是多格)可跳过逐格 kind 查询 const paintSelectionOuterFrame = dataGridSelectionUsesOuterFrame(selectionFrames); @@ -485,13 +489,17 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions): boolean ctx.beginPath(); ctx.rect(0, y, rowNumberWidth, CANVAS_DATA_GRID_ROW_HEIGHT); ctx.clip(); - if (item.isDraft) { - ctx.fillText("*", rowNumberTextX, textY); - } else if (infiniteScrollEnabled) { - ctx.fillText(String(item.displayIndex + 1), rowNumberTextX, textY); - } else { - ctx.fillText(String(item.displayIndex + 1 + pageOffset), rowNumberTextX, textY); - } + ctx.fillText( + resolveDataGridRowNumberLabel({ + displayIndex: item.displayIndex, + sourceIndex: item.sourceIndex, + isDraft: item.isDraft, + sourceRowNumbers: rowNumberMode === "source", + pageOffset: infiniteScrollEnabled ? 0 : pageOffset, + }), + rowNumberTextX, + textY, + ); ctx.restore(); ctx.font = normalFont; diff --git a/apps/desktop/src/lib/dataGrid/dataGridRowNumber.ts b/apps/desktop/src/lib/dataGrid/dataGridRowNumber.ts new file mode 100644 index 0000000000..b54127be02 --- /dev/null +++ b/apps/desktop/src/lib/dataGrid/dataGridRowNumber.ts @@ -0,0 +1,25 @@ +/** + * 行号栏取值。 + * + * `displayIndex` 是当前视图里的压缩序号:一旦应用筛选(Ctrl+F 过滤模式、列头筛选、 + * 行状态筛选),被隐藏的行后面的行号会整体前移,无法再对应回完整结果里的位置。 + * `sourceIndex` 是筛选前的原始下标,一直保留在行数据里,因此 `source` 模式直接用它, + * 让筛选前后同一个行号始终指向同一条数据。分页模式下原始下标是页内下标, + * 仍需叠加页偏移才能得到完整结果中的位置。 + */ +export interface DataGridRowNumberInput { + displayIndex: number; + sourceIndex?: number; + isDraft?: boolean; + /** true = 显示原始行号(sourceIndex),false = 显示当前视图序号(displayIndex) */ + sourceRowNumbers: boolean; + /** 已加载行之前的行数:分页页码偏移;无限滚动 / 单页时为 0 */ + pageOffset: number; +} + +export function resolveDataGridRowNumberLabel(input: DataGridRowNumberInput): string { + // 待保存的草稿行没有落库位置,保持原有的 "*" 占位 + if (input.isDraft) return "*"; + const index = input.sourceRowNumbers && input.sourceIndex !== undefined ? input.sourceIndex : input.displayIndex; + return String(index + 1 + input.pageOffset); +} diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts index d32bf1b358..6b159d9155 100644 --- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts +++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts @@ -371,6 +371,12 @@ describe("normalizeEditorSettings", () => { expect(normalizeEditorSettings({ dataGridSearchMode: "invalid" as any }).dataGridSearchMode).toBe("filter"); }); + it("defaults the data grid row number column to the view position and preserves original row numbers", () => { + expect(normalizeEditorSettings({}).dataGridRowNumberMode).toBe("view"); + expect(normalizeEditorSettings({ dataGridRowNumberMode: "source" }).dataGridRowNumberMode).toBe("source"); + expect(normalizeEditorSettings({ dataGridRowNumberMode: "invalid" as any }).dataGridRowNumberMode).toBe("view"); + }); + it("defaults the global data grid copy preference and preserves valid choices", () => { expect(normalizeEditorSettings({}).dataGridCopyExtractor).toBe("smart"); expect(normalizeEditorSettings({ dataGridCopyExtractor: "smart" }).dataGridCopyExtractor).toBe("smart"); diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 44a600a85c..c6633a4285 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -663,6 +663,8 @@ const DATA_GRID_RENDER_MODES = ["dom", "canvas"] as const; export type DataGridRenderMode = (typeof DATA_GRID_RENDER_MODES)[number]; const DATA_GRID_SEARCH_MODES = ["filter", "highlight"] as const; export type DataGridSearchMode = (typeof DATA_GRID_SEARCH_MODES)[number]; +const DATA_GRID_ROW_NUMBER_MODES = ["view", "source"] as const; +export type DataGridRowNumberMode = (typeof DATA_GRID_ROW_NUMBER_MODES)[number]; export type DataGridFilterEditorView = "quick" | "conditions" | "text"; export type DataGridToolbarLayout = "single" | "split"; const RESULT_RUN_DISPLAY_MODES = ["tabs", "list"] as const; @@ -852,6 +854,7 @@ export interface EditorSettings { localFilterPopoverWidth: number; dataGridRenderMode: DataGridRenderMode; dataGridSearchMode: DataGridSearchMode; + dataGridRowNumberMode: DataGridRowNumberMode; dataGridCopyExtractor: DataGridCopyPreference; dataGridExtractorOptions: DataGridExtractorOptions; dataGridExtractorOptionsMigrationVersion: number; @@ -1127,6 +1130,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { localFilterPopoverWidth: 360, dataGridRenderMode: "canvas", dataGridSearchMode: "filter", + dataGridRowNumberMode: "view", dataGridCopyExtractor: "smart", dataGridExtractorOptions: normalizeDataGridExtractorOptions(DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS), dataGridExtractorOptionsMigrationVersion: DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION, @@ -1300,6 +1304,10 @@ function normalizeDataGridSearchMode(value: unknown): DataGridSearchMode { return DATA_GRID_SEARCH_MODES.includes(value as DataGridSearchMode) ? (value as DataGridSearchMode) : DEFAULT_EDITOR_SETTINGS.dataGridSearchMode; } +function normalizeDataGridRowNumberMode(value: unknown): DataGridRowNumberMode { + return DATA_GRID_ROW_NUMBER_MODES.includes(value as DataGridRowNumberMode) ? (value as DataGridRowNumberMode) : DEFAULT_EDITOR_SETTINGS.dataGridRowNumberMode; +} + function normalizeDataGridFilterEditorView(value: unknown): DataGridFilterEditorView { return value === "conditions" || value === "text" ? value : DEFAULT_EDITOR_SETTINGS.dataGridFilterEditorView; } @@ -1675,6 +1683,7 @@ export function normalizeEditorSettings(settings: Partial, exist localFilterPopoverWidth: normalizeDrawerWidth(settings.localFilterPopoverWidth, 240, DEFAULT_EDITOR_SETTINGS.localFilterPopoverWidth), dataGridRenderMode: normalizeDataGridRenderMode(settings.dataGridRenderMode), dataGridSearchMode: normalizeDataGridSearchMode(settings.dataGridSearchMode), + dataGridRowNumberMode: normalizeDataGridRowNumberMode(settings.dataGridRowNumberMode), dataGridCopyExtractor: normalizeDataGridCopyPreference(settings.dataGridCopyExtractor), dataGridExtractorOptions, dataGridExtractorOptionsMigrationVersion: DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION, @@ -2499,6 +2508,7 @@ export const useSettingsStore = defineStore("settings", () => { if (partial.localFilterPopoverWidth !== undefined) editorSettings.value.localFilterPopoverWidth = normalizeDrawerWidth(partial.localFilterPopoverWidth, 240, DEFAULT_EDITOR_SETTINGS.localFilterPopoverWidth); if (partial.dataGridRenderMode !== undefined) editorSettings.value.dataGridRenderMode = normalizeDataGridRenderMode(partial.dataGridRenderMode); if (partial.dataGridSearchMode !== undefined) editorSettings.value.dataGridSearchMode = normalizeDataGridSearchMode(partial.dataGridSearchMode); + if (partial.dataGridRowNumberMode !== undefined) editorSettings.value.dataGridRowNumberMode = normalizeDataGridRowNumberMode(partial.dataGridRowNumberMode); if (partial.dataGridCopyExtractor !== undefined) editorSettings.value.dataGridCopyExtractor = normalizeDataGridCopyPreference(partial.dataGridCopyExtractor); if (partial.dataGridExtractorOptions !== undefined) editorSettings.value.dataGridExtractorOptions = normalizeDataGridExtractorOptions(partial.dataGridExtractorOptions); if (partial.dataGridExtractorOptionsMigrationVersion !== undefined) editorSettings.value.dataGridExtractorOptionsMigrationVersion = DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION;