feat(data-grid): make the row number column show original row numbers
This commit is contained in:
1 parent
9ab1462ca8
commit
6517284d95
17 files changed
+196
-10
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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({
|
||||
</div>
|
||||
</LightTooltip>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-3 px-3 py-1.5 text-xs">
|
||||
<div class="min-w-0 flex items-center gap-2 font-medium">
|
||||
<Hash class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<span>{{ t("grid.rowNumberMode") }}</span>
|
||||
</div>
|
||||
<LightTooltip :text="t('grid.rowNumberModeHint')" side="left" :side-offset="6" :delay="0" :open-on-focus="false">
|
||||
<div class="grid w-32 grid-cols-2 rounded-md border bg-muted/40 p-0.5">
|
||||
<button
|
||||
type="button"
|
||||
class="h-5 min-w-0 truncate whitespace-nowrap rounded-[5px] px-2 text-xs transition-colors"
|
||||
:class="dataGridRowNumberMode === 'view' ? 'bg-background font-semibold text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'"
|
||||
@click="setDataGridRowNumberMode('view')"
|
||||
>
|
||||
{{ t("grid.rowNumberModeView") }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="h-5 min-w-0 truncate whitespace-nowrap rounded-[5px] px-2 text-xs transition-colors"
|
||||
:class="dataGridRowNumberMode === 'source' ? 'bg-background font-semibold text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'"
|
||||
@click="setDataGridRowNumberMode('source')"
|
||||
>
|
||||
{{ t("grid.rowNumberModeSource") }}
|
||||
</button>
|
||||
</div>
|
||||
</LightTooltip>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-3 px-3 py-1.5 text-xs">
|
||||
<div class="min-w-0 flex items-center gap-2 font-medium">
|
||||
<Rows3 class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
@@ -2425,6 +2457,32 @@ defineExpose({
|
||||
</div>
|
||||
</LightTooltip>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-3 px-3 py-1.5 text-xs">
|
||||
<div class="min-w-0 flex items-center gap-2 font-medium">
|
||||
<Hash class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<span>{{ t("grid.rowNumberMode") }}</span>
|
||||
</div>
|
||||
<LightTooltip :text="t('grid.rowNumberModeHint')" side="left" :side-offset="6" :delay="0" :open-on-focus="false">
|
||||
<div class="grid w-32 grid-cols-2 rounded-md border bg-muted/40 p-0.5">
|
||||
<button
|
||||
type="button"
|
||||
class="h-5 min-w-0 truncate whitespace-nowrap rounded-[5px] px-2 text-xs transition-colors"
|
||||
:class="dataGridRowNumberMode === 'view' ? 'bg-background font-semibold text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'"
|
||||
@click="setDataGridRowNumberMode('view')"
|
||||
>
|
||||
{{ t("grid.rowNumberModeView") }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="h-5 min-w-0 truncate whitespace-nowrap rounded-[5px] px-2 text-xs transition-colors"
|
||||
:class="dataGridRowNumberMode === 'source' ? 'bg-background font-semibold text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'"
|
||||
@click="setDataGridRowNumberMode('source')"
|
||||
>
|
||||
{{ t("grid.rowNumberModeSource") }}
|
||||
</button>
|
||||
</div>
|
||||
</LightTooltip>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-3 px-3 py-1.5 text-xs">
|
||||
<div class="min-w-0 flex items-center gap-2 font-medium">
|
||||
<Rows3 class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -2601,6 +2601,10 @@ export default withEnglishFallback({
|
||||
searchModeHint: "Ctrl+F 検索時に一致する行をフィルタリングするか、全行を表示したまま一致箇所をハイライトするかを選択します。",
|
||||
searchModeFilter: "フィルター",
|
||||
searchModeHighlight: "ハイライト",
|
||||
rowNumberMode: "行番号の表示",
|
||||
rowNumberModeHint: "行番号欄に現在の表示位置とフィルター前の元の行番号のどちらを表示するか選択します。元の行番号は絞り込み後も変わりません。",
|
||||
rowNumberModeView: "表示位置",
|
||||
rowNumberModeSource: "元の行番号",
|
||||
emptyStringValue: "空文字",
|
||||
jumpToPage: "ページ番号に移動",
|
||||
importBinaryValue: "ファイルからインポート",
|
||||
|
||||
@@ -2140,6 +2140,10 @@ export default withEnglishFallback({
|
||||
searchModeHint: "Ctrl+F가 일치하는 행을 필터링할지, 모든 행을 표시하고 일치 항목을 강조할지 선택하세요.",
|
||||
searchModeFilter: "필터",
|
||||
searchModeHighlight: "강조",
|
||||
rowNumberMode: "행 번호 표시",
|
||||
rowNumberModeHint: "행 번호 열에 현재 보기 위치와 필터링 전 원래 행 번호 중 무엇을 표시할지 선택하세요. 원래 행 번호는 필터링 후에도 바뀌지 않습니다.",
|
||||
rowNumberModeView: "보기 위치",
|
||||
rowNumberModeSource: "원래 행 번호",
|
||||
colorizeDataTypes: "데이터 타입 색상",
|
||||
numericColumnAlign: "숫자 컬럼 정렬",
|
||||
numericColumnAlignLeft: "왼쪽",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -2214,6 +2214,10 @@ export default withEnglishFallback({
|
||||
searchModeHint: "选择 Ctrl+F 搜索时过滤匹配行,或保留全部行并高亮定位命中项。",
|
||||
searchModeFilter: "过滤",
|
||||
searchModeHighlight: "高亮",
|
||||
rowNumberMode: "行号显示方式",
|
||||
rowNumberModeHint: "选择行号栏显示当前视图序号,或筛选前的原始行号;原始行号在筛选、搜索后保持不变,便于定位数据在完整结果中的位置。",
|
||||
rowNumberModeView: "视图序号",
|
||||
rowNumberModeSource: "原始行号",
|
||||
colorizeDataTypes: "数据类型着色",
|
||||
numericColumnAlign: "数值列对齐",
|
||||
numericColumnAlignLeft: "左对齐",
|
||||
|
||||
@@ -2552,6 +2552,10 @@ export default withEnglishFallback({
|
||||
},
|
||||
cachedResultUnavailable: "快取結果缺失或不相容。",
|
||||
reexecuteQuery: "重新執行查詢",
|
||||
rowNumberMode: "行號顯示方式",
|
||||
rowNumberModeHint: "選擇行號欄顯示目前檢視序號,或篩選前的原始行號;原始行號在篩選、搜尋後保持不變,便於定位資料在完整結果中的位置。",
|
||||
rowNumberModeView: "檢視序號",
|
||||
rowNumberModeSource: "原始行號",
|
||||
colorizeDataTypes: "資料類型著色",
|
||||
numericColumnAlign: "數值列對齊",
|
||||
numericColumnAlignLeft: "左對齊",
|
||||
|
||||
@@ -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("*");
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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<EditorSettings>, 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;
|
||||
|
||||
Reference in new issue
Block a user