From 89ecc63b9e8d1f1cbb364f9452bb5de8344f79fc Mon Sep 17 00:00:00 2001 From: lxk955 <164191365+lxk955@users.noreply.github.com> Date: Sun, 4 Oct 2026 10:31:39 +0800 Subject: [PATCH] feat(grid): customize zebra stripe color --- .../editor/EditorSettingsDialog.vue | 23 ++++++++++++++++++- apps/desktop/src/components/grid/DataGrid.vue | 6 +++-- apps/desktop/src/i18n/locales/az.ts | 1 + apps/desktop/src/i18n/locales/en.ts | 1 + apps/desktop/src/i18n/locales/es.ts | 1 + apps/desktop/src/i18n/locales/id.ts | 1 + apps/desktop/src/i18n/locales/it.ts | 1 + apps/desktop/src/i18n/locales/ja.ts | 1 + apps/desktop/src/i18n/locales/ko.ts | 1 + apps/desktop/src/i18n/locales/pt-BR.ts | 1 + apps/desktop/src/i18n/locales/ru.ts | 1 + apps/desktop/src/i18n/locales/tr.ts | 1 + apps/desktop/src/i18n/locales/zh-CN.ts | 1 + apps/desktop/src/i18n/locales/zh-TW.ts | 1 + .../canvasDataGridRendererFrozen.spec.ts | 22 ++++++++++++++++++ .../lib/dataGrid/canvasDataGridRenderer.ts | 21 +++++++++++++---- .../__tests__/editorSettingsDraft.spec.ts | 4 ++++ .../__tests__/settingsTransfer.spec.ts | 13 +++++++++++ .../src/lib/settings/editorSettingsDraft.ts | 1 + .../src/lib/settings/settingsTransfer.ts | 2 ++ .../stores/__tests__/settingsStore.spec.ts | 6 +++++ apps/desktop/src/stores/settingsStore.ts | 4 ++++ 22 files changed, 107 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index d94a049b5d..5423fb0108 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -758,6 +758,7 @@ const editDataGridCellDetailButtonVisible = ref(settingsStore.editorSettings.dat const editDataGridCellDetailDialogDefault = ref(settingsStore.editorSettings.dataGridCellDetailDialogDefault); const editDataGridCrosshairHighlight = ref(settingsStore.editorSettings.dataGridCrosshairHighlight); const editDataGridStripedRows = ref(settingsStore.editorSettings.dataGridStripedRows); +const editDataGridZebraRowBg = ref(settingsStore.editorSettings.dataGridZebraRowBg); const editPageSize = ref(settingsStore.editorSettings.pageSize); const editTableOpenPageSize = ref(settingsStore.editorSettings.tableOpenPageSize); const editTableOpenSortMode = ref(settingsStore.editorSettings.tableOpenSortMode); @@ -1117,6 +1118,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft { dataGridCellDetailDialogDefault: editDataGridCellDetailDialogDefault.value, dataGridCrosshairHighlight: editDataGridCrosshairHighlight.value, dataGridStripedRows: editDataGridStripedRows.value, + dataGridZebraRowBg: editDataGridZebraRowBg.value, flatteningMultiLineText: editFlatteningMultiLineText.value, dataGridShowWhitespace: editDataGridShowWhitespace.value, pageSize: editPageSize.value, @@ -1791,6 +1793,7 @@ function syncEditorSettingsDraftFromStore() { editDataGridCellDetailDialogDefault.value = settingsStore.editorSettings.dataGridCellDetailDialogDefault; editDataGridCrosshairHighlight.value = settingsStore.editorSettings.dataGridCrosshairHighlight; editDataGridStripedRows.value = settingsStore.editorSettings.dataGridStripedRows; + editDataGridZebraRowBg.value = settingsStore.editorSettings.dataGridZebraRowBg; editFlatteningMultiLineText.value = settingsStore.editorSettings.flatteningMultiLineText; editDataGridShowWhitespace.value = settingsStore.editorSettings.dataGridShowWhitespace; editPageSize.value = settingsStore.editorSettings.pageSize; @@ -1940,6 +1943,7 @@ const editorSettingsDraftRefs: EditorSettingsDraftRefMap = { dataGridCellDetailDialogDefault: editDataGridCellDetailDialogDefault, dataGridCrosshairHighlight: editDataGridCrosshairHighlight, dataGridStripedRows: editDataGridStripedRows, + dataGridZebraRowBg: editDataGridZebraRowBg, pageSize: editPageSize, tableOpenPageSize: editTableOpenPageSize, tableOpenSortMode: editTableOpenSortMode, @@ -2525,6 +2529,7 @@ function resetDefaultsForTab(tab: SettingsCategory) { editDataGridCellDetailDialogDefault.value = DEFAULT_EDITOR_SETTINGS.dataGridCellDetailDialogDefault; editDataGridCrosshairHighlight.value = DEFAULT_EDITOR_SETTINGS.dataGridCrosshairHighlight; editDataGridStripedRows.value = DEFAULT_EDITOR_SETTINGS.dataGridStripedRows; + editDataGridZebraRowBg.value = DEFAULT_EDITOR_SETTINGS.dataGridZebraRowBg; editFlatteningMultiLineText.value = DEFAULT_EDITOR_SETTINGS.flatteningMultiLineText; editDataGridShowWhitespace.value = DEFAULT_EDITOR_SETTINGS.dataGridShowWhitespace; editPageSize.value = DEFAULT_EDITOR_SETTINGS.pageSize; @@ -2642,6 +2647,7 @@ function resetAllDefaults() { editDataGridCellDetailDialogDefault.value = DEFAULT_EDITOR_SETTINGS.dataGridCellDetailDialogDefault; editDataGridCrosshairHighlight.value = DEFAULT_EDITOR_SETTINGS.dataGridCrosshairHighlight; editDataGridStripedRows.value = DEFAULT_EDITOR_SETTINGS.dataGridStripedRows; + editDataGridZebraRowBg.value = DEFAULT_EDITOR_SETTINGS.dataGridZebraRowBg; editFlatteningMultiLineText.value = DEFAULT_EDITOR_SETTINGS.flatteningMultiLineText; editDataGridShowWhitespace.value = DEFAULT_EDITOR_SETTINGS.dataGridShowWhitespace; editPageSize.value = DEFAULT_EDITOR_SETTINGS.pageSize; @@ -8772,7 +8778,22 @@ onUnmounted(() => { {{ t("settings.dataGridStripedRowsDescription") }}

- +
+
+ + + +
+ +
diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index de23a841fd..f33ce40e49 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -7030,7 +7030,7 @@ function dataGridRowStyle(item: RowItem): CSSProperties { ? "rgb(51, 51, 55)" : "rgb(243, 243, 243)" : dataGridStripedRows.value && item.displayIndex % 2 === 1 - ? `var(--data-grid-row-muted-bg, ${dark ? DATA_GRID_DARK_STRIPED_ROW_BG : DATA_GRID_LIGHT_STRIPED_ROW_BG})` + ? settingsStore.editorSettings.dataGridZebraRowBg?.trim() || `var(--data-grid-row-muted-bg, ${dark ? DATA_GRID_DARK_STRIPED_ROW_BG : DATA_GRID_LIGHT_STRIPED_ROW_BG})` : "var(--data-grid-background)"; const rowNumberBg = item.status === "new" @@ -7107,7 +7107,8 @@ const dataGridTypeColorKey = computed(() => { return colors ? DATA_GRID_TYPE_COLOR_KEYS.map((key) => colors[key]).join(",") : "auto"; }); const canvasRenderStyleKey = computed( - () => `${settingsStore.editorSettings.theme}:${settingsStore.editorSettings.uiScale}:${canvasBackingPixelRatio.value}:${isDark.value}:${themePalette.value}:${tableFontFamily.value}:${tableFontSize.value}:${!!saveError.value}:${dataGridTypeColorKey.value}:${dataGridStripedRows.value}`, + () => + `${settingsStore.editorSettings.theme}:${settingsStore.editorSettings.uiScale}:${canvasBackingPixelRatio.value}:${isDark.value}:${themePalette.value}:${tableFontFamily.value}:${tableFontSize.value}:${!!saveError.value}:${dataGridTypeColorKey.value}:${dataGridStripedRows.value}:${settingsStore.editorSettings.dataGridZebraRowBg}`, ); const CANVAS_MOUSE_WHEEL_SCROLL_MULTIPLIER = 1.5; const CANVAS_TRACKPAD_DELTA_THRESHOLD = 40; @@ -7884,6 +7885,7 @@ function drawCanvasGrid() { flatteningMultiLineEnabled: flatteningMultiLineEnabled.value, showWhitespace: showWhitespaceEnabled.value, stripedRows: dataGridStripedRows.value, + zebraRowBg: settingsStore.editorSettings.dataGridZebraRowBg, rowNumberMode: dataGridRowNumberMode.value, }); if (!drawn) return; diff --git a/apps/desktop/src/i18n/locales/az.ts b/apps/desktop/src/i18n/locales/az.ts index 42195a56bb..bf59d69ccb 100644 --- a/apps/desktop/src/i18n/locales/az.ts +++ b/apps/desktop/src/i18n/locales/az.ts @@ -7286,6 +7286,7 @@ export default withEnglishFallback({ dataGridCellDetailDialogDefaultDescription: "Xana təfərrüatları düyməsinə kliklədikdə yan və ya aşağı panel yerinə standart olaraq modal dialoq pəncərəsində açır.", dataGridCrosshairHighlight: "Sətir və sütunun çarpaz vurğulanması", dataGridCrosshairHighlightDescription: "Aktiv olduqda aktiv xananın bütün sətrini və sütununu yüngülcə vurğula (Excel-dəki kimi). Fokuslanmış xana seçilmiş üslubunu saxlayır.", + dataGridZebraRowBg: "Zolaq rəngi", infiniteScroll: "Sonsuz sürüşdürmə ilə yükləmə", autoCalculateTotalRows: "Ümumi sətir sayını avtomatik hesabla", autoCalculateTotalRowsDescription: "Uyğun gələn sətirlərin ümumi sayını göstərmək üçün hər sorğudan sonra COUNT(*) avtomatik işlədilir. Böyük sorğuların sürətini qorumaq üçün standart olaraq söndürülüb — ehtiyac olduqda nəticənin alt hissəsindən hesablayın.", diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 740197aa65..b130a17c0b 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -8163,6 +8163,7 @@ export default { dataGridCellDetailDialogDefaultDescription: "Open cell details in a modal dialog by default instead of the side or bottom panel.", dataGridCrosshairHighlight: "Crosshair row & column highlight", dataGridCrosshairHighlightDescription: "When enabled, lightly highlight the entire row and column of the active cell (like Excel). The focused cell keeps its selected style.", + dataGridZebraRowBg: "Stripe Color", 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.", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 1d5fa7bf4c..dbba013884 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -7497,6 +7497,7 @@ export default withEnglishFallback({ dataGridCellDetailDialogDefaultDescription: "Abre los detalles de celda en un diálogo modal por defecto en lugar del panel lateral o inferior.", dataGridCrosshairHighlight: "Resaltado de fila y columna en cruz", dataGridCrosshairHighlightDescription: "Cuando esté activado, resalta ligeramente toda la fila y la columna de la celda activa (como Excel). La celda enfocada conserva su estilo seleccionado.", + dataGridZebraRowBg: "Color de fila alterna", 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.", diff --git a/apps/desktop/src/i18n/locales/id.ts b/apps/desktop/src/i18n/locales/id.ts index 74efd7e63f..2d9597e3b6 100644 --- a/apps/desktop/src/i18n/locales/id.ts +++ b/apps/desktop/src/i18n/locales/id.ts @@ -7964,6 +7964,7 @@ export default withEnglishFallback({ dataGridShowWhitespaceDescription: "Tampilkan spasi sebagai · dan tab sebagai → pada sel data. Nilai yang disalin dan diedit tetap tidak berubah.", dataGridStripedRows: "Garis zebra", dataGridStripedRowsDescription: "Jika diaktifkan, baris ganjil dan genap pada kisi data akan menampilkan warna latar belakang yang berbeda.", + dataGridZebraRowBg: "Warna garis zebra", flatteningMultiLineText: "Meratakan Teks Multi-baris", flatteningMultiLineTextDescription: "Teks multi-baris digabungkan menjadi tampilan satu baris, dengan pemutus baris ditampilkan sebagai ¶.", regexMaxMatchCount: "Jumlah maksimum hasil cocok pilih-semua", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index 295bee9391..12e0ab9012 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -7275,6 +7275,7 @@ export default withEnglishFallback({ dataGridCellDetailDialogDefaultDescription: "Apre i dettagli della cella in una finestra di dialogo modale per impostazione predefinita anziché nel pannello laterale o inferiore.", dataGridCrosshairHighlight: "Evidenzia riga e colonna a croce", dataGridCrosshairHighlightDescription: "Se attivato, evidenzia leggermente l'intera riga e la colonna della cella attiva (come Excel). La cella focalizzata mantiene il suo stile selezionato.", + dataGridZebraRowBg: "Colore riga alterna", 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.", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index ba4472c1df..d733d32ddd 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -7302,6 +7302,7 @@ export default withEnglishFallback({ dataGridCellDetailDialogDefaultDescription: "セル詳細ボタンをクリックしたときに、サイドや下部パネルではなくモーダルダイアログで表示します。", dataGridCrosshairHighlight: "行と列の十字ハイライト", dataGridCrosshairHighlightDescription: "有効にすると、アクティブなセルの行と列全体を淡くハイライトします(Excel と同様)。フォーカスされたセルは選択スタイルを維持します。", + dataGridZebraRowBg: "縞模様の色", tableColumnTemplateFields: "新規テーブルのプリセット列", tableColumnTemplateFieldsDescription: "データベース種別を選択し、新規テーブル作成時に使うプリセット列の型を設定します。", tableColumnTemplateAdd: "列を追加", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index 52fbd930ae..7466ed26ac 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -7079,6 +7079,7 @@ export default withEnglishFallback({ dataGridCellDetailDialogDefaultDescription: "셀 세부 정보 버튼을 클릭할 때 사이드 또는 하단 패널 대신 모달 대화상자로 표시합니다.", dataGridCrosshairHighlight: "행·열 십자 강조", dataGridCrosshairHighlightDescription: "활성화하면 활성 셀의 전체 행과 열을 연하게 강조합니다(Excel과 유사). 포커스 셀은 기존 선택 스타일을 유지합니다.", + dataGridZebraRowBg: "줄무늬 색상", infiniteScroll: "무한 스크롤 로딩", autoCalculateTotalRows: "전체 행 수 자동 집계", autoCalculateTotalRowsDescription: "각 쿼리 후 자동으로 COUNT(*)를 실행하여 일치하는 전체 행 수를 표시합니다. 대규모 쿼리를 빠르게 유지하기 위해 기본적으로 꺼져 있으며, 결과 푸터에서 요청 시 집계할 수 있습니다.", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 8fdca77d32..1c860901cb 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -7277,6 +7277,7 @@ export default withEnglishFallback({ dataGridCellDetailDialogDefaultDescription: "Abre os detalhes da célula em uma caixa de diálogo modal por padrão, em vez do painel lateral ou inferior.", dataGridCrosshairHighlight: "Destacar linha e coluna em cruz", dataGridCrosshairHighlightDescription: "Quando ativado, destaca levemente toda a linha e a coluna da célula ativa (como no Excel). A célula focada mantém o estilo selecionado.", + dataGridZebraRowBg: "Cor da linha alternada", 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.", diff --git a/apps/desktop/src/i18n/locales/ru.ts b/apps/desktop/src/i18n/locales/ru.ts index dc318c28a5..ccebdb240d 100644 --- a/apps/desktop/src/i18n/locales/ru.ts +++ b/apps/desktop/src/i18n/locales/ru.ts @@ -8481,6 +8481,7 @@ export default withEnglishFallback({ dataGridCellDetailDialogDefaultDescription: "Открывать сведения о ячейке в модальном окне по умолчанию вместо боковой или нижней панели.", dataGridCrosshairHighlight: "Подсветка строки и столбца перекрестием", dataGridCrosshairHighlightDescription: "Если включено, слегка подсвечиваются вся строка и столбец активной ячейки (как в Excel). Ячейка в фокусе сохраняет свой выделенный стиль.", + dataGridZebraRowBg: "Цвет полосы", infiniteScroll: "Бесконечная прокрутка с подгрузкой", autoCalculateTotalRows: "Автоматически считать общее число строк", autoCalculateTotalRowsDescription: "Автоматически выполнять COUNT(*) после каждого запроса, чтобы показать общее число подходящих строк. По умолчанию выключено, чтобы большие запросы оставались быстрыми — счёт можно запустить вручную из нижней панели результатов.", diff --git a/apps/desktop/src/i18n/locales/tr.ts b/apps/desktop/src/i18n/locales/tr.ts index be522f71a2..416aead11d 100644 --- a/apps/desktop/src/i18n/locales/tr.ts +++ b/apps/desktop/src/i18n/locales/tr.ts @@ -7170,6 +7170,7 @@ export default withEnglishFallback({ dataGridCellDetailDialogDefaultDescription: "Hücre ayrıntıları düğmesine tıklandığında yan veya alt panel yerine varsayılan olarak bir modal iletişim kutusunda açar.", dataGridCrosshairHighlight: "Artı imleç satır ve sütun vurgusu", dataGridCrosshairHighlightDescription: "Etkinleştirildiğinde etkin hücrenin tüm satırı ve sütunu hafifçe vurgulanır (Excel gibi). Odaklanan hücre kendi seçili biçemini korur.", + dataGridZebraRowBg: "Çizgi Rengi", infiniteScroll: "Sonsuz kaydırmayla yükleme", autoCalculateTotalRows: "Toplam satır sayısını otomatik hesapla", autoCalculateTotalRowsDescription: "Eşleşen toplam satırı göstermek için her sorgunun ardından otomatik olarak COUNT(*) çalıştır. Büyük sorguların hızlı kalması için varsayılan olarak kapalıdır — sonuç alt bilgisinden istediğinizde hesaplayın.", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index c8ae293e04..c1d81476dc 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -8149,6 +8149,7 @@ export default withEnglishFallback({ dataGridCellDetailDialogDefaultDescription: "点击单元格详情按钮时,默认使用弹窗展示而非侧边或下方分屏。", dataGridCrosshairHighlight: "行列十字高亮", dataGridCrosshairHighlightDescription: "开启后,以当前活动单元格为中心淡色高亮整行和整列(类似 Excel)。焦点单元格仍保留原有的选中样式。", + dataGridZebraRowBg: "斑马纹颜色", infiniteScroll: "无限滚动加载", autoCalculateTotalRows: "自动统计总行数", autoCalculateTotalRowsDescription: "每次查询后自动执行 COUNT(*) 显示匹配的总行数。默认关闭以保证大查询速度 —— 可在结果栏按需手动统计。", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index b422a113b7..a43181907e 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -6625,6 +6625,7 @@ export default withEnglishFallback({ dataGridCellDetailDialogDefaultDescription: "點擊儲存格詳情按鈕時,預設使用彈窗展示而非側邊或下方分屏。", dataGridCrosshairHighlight: "行列十字高亮", dataGridCrosshairHighlightDescription: "開啟後,以目前活動儲存格為中心淡色高亮整列和整行(類似 Excel)。焦點儲存格仍保留原有選取樣式。", + dataGridZebraRowBg: "斑馬紋顏色", infiniteScroll: "無限滾動載入", autoCalculateTotalRows: "自動統計總筆數", autoCalculateTotalRowsDescription: "每次查詢後自動執行 COUNT(*) 顯示符合的總筆數。預設關閉以確保大型查詢速度 —— 可在結果列按需手動統計。", diff --git a/apps/desktop/src/lib/__tests__/dataGrid/canvasDataGridRendererFrozen.spec.ts b/apps/desktop/src/lib/__tests__/dataGrid/canvasDataGridRendererFrozen.spec.ts index ebd94bb2ce..70a18d6e61 100644 --- a/apps/desktop/src/lib/__tests__/dataGrid/canvasDataGridRendererFrozen.spec.ts +++ b/apps/desktop/src/lib/__tests__/dataGrid/canvasDataGridRendererFrozen.spec.ts @@ -237,4 +237,26 @@ describe("drawCanvasDataGrid with frozen columns", () => { }); expect(() => drawCanvasDataGrid(options)).not.toThrow(); }); + + it("draws with zebraStriping enabled and disabled", () => { + const canvas = createMockCanvas(); + const optionsWithZebra = createBaseOptions({ + canvas, + zebraStriping: true, + }); + expect(() => drawCanvasDataGrid(optionsWithZebra)).not.toThrow(); + + const optionsWithoutZebra = createBaseOptions({ + canvas, + zebraStriping: false, + }); + expect(() => drawCanvasDataGrid(optionsWithoutZebra)).not.toThrow(); + + const optionsWithCustomZebraColor = createBaseOptions({ + canvas, + zebraStriping: true, + zebraRowBg: "#334455", + }); + expect(() => drawCanvasDataGrid(optionsWithCustomZebraColor)).not.toThrow(); + }); }); diff --git a/apps/desktop/src/lib/dataGrid/canvasDataGridRenderer.ts b/apps/desktop/src/lib/dataGrid/canvasDataGridRenderer.ts index fcef0c3516..3eb8569c79 100644 --- a/apps/desktop/src/lib/dataGrid/canvasDataGridRenderer.ts +++ b/apps/desktop/src/lib/dataGrid/canvasDataGridRenderer.ts @@ -104,6 +104,8 @@ export interface DrawCanvasDataGridOptions { flatteningMultiLineEnabled: boolean; showWhitespace?: boolean; stripedRows?: boolean; + zebraStriping?: boolean; + zebraRowBg?: string; /** 行号栏取值:`view` = 当前视图序号(默认,与筛选前一致),`source` = 筛选前的原始行号 */ rowNumberMode?: "view" | "source"; } @@ -153,12 +155,18 @@ export function resolveCanvasBackingStoreMetrics(options: { width: number; heigh }; } -export function resolveCanvasDataGridRowBase(theme: Pick, item: Pick, options: { isActive: boolean; stripedRows?: boolean }): string { - const { isActive, stripedRows = true } = options; +export function resolveCanvasDataGridRowBase( + theme: Pick, + item: Pick, + options: { isActive: boolean; stripedRows?: boolean; zebraStriping?: boolean; zebraRowBg?: string }, +): string { + const { isActive } = options; + const stripedRows = options.stripedRows ?? options.zebraStriping ?? true; + const stripedRowBg = options.zebraRowBg?.trim() || theme.rowMuted; if (item.isDeleted) return theme.rowDeleted; if (item.isNew && !isActive) return theme.rowNew; if (item.isDraft && !isActive) return theme.rowMuted; - if (stripedRows && item.displayIndex % 2 === 1 && !isActive) return theme.rowMuted; + if (stripedRows && item.displayIndex % 2 === 1 && !isActive) return stripedRowBg; return theme.background; } @@ -378,9 +386,12 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions): boolean booleanDisplayMode = "dropdown", flatteningMultiLineEnabled, showWhitespace = false, - stripedRows = true, + stripedRows: rawStripedRows, + zebraStriping, + zebraRowBg, rowNumberMode = "view", } = options; + const stripedRows = rawStripedRows ?? zebraStriping ?? true; // 框选热路径:整次绘制只判断一次。常见情况(单矩形 / 多列且每段都是多格)可跳过逐格 kind 查询 const paintSelectionOuterFrame = dataGridSelectionUsesOuterFrame(selectionFrames); const suppressAllSelectedCellBorders = selectionFrames.length > 0 && selectionFrames.every(dataGridFrameIsMultiCell); @@ -444,6 +455,8 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions): boolean const rowBase = resolveCanvasDataGridRowBase(theme, item, { isActive: rowIsActive, stripedRows, + zebraStriping, + zebraRowBg, }); const rowFill = resolveCanvasDataGridRowFill(theme, rowBase, { isActive: rowIsActive, diff --git a/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts b/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts index d1de1fd003..aaa2ee3791 100644 --- a/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts +++ b/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts @@ -143,6 +143,10 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => { expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("dataGridCellDetailButtonVisible"); }); + it("includes zebra row background in draft keys", () => { + expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("dataGridZebraRowBg"); + }); + it("includes dataGridCellDetailDialogDefault", () => { expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("dataGridCellDetailDialogDefault"); }); diff --git a/apps/desktop/src/lib/settings/__tests__/settingsTransfer.spec.ts b/apps/desktop/src/lib/settings/__tests__/settingsTransfer.spec.ts index 1710f5f1af..3eb6f6cd0e 100644 --- a/apps/desktop/src/lib/settings/__tests__/settingsTransfer.spec.ts +++ b/apps/desktop/src/lib/settings/__tests__/settingsTransfer.spec.ts @@ -475,4 +475,17 @@ describe("settingsTransfer", () => { expect(transferCategoryForKey("csvNullMode")).toBe("data"); expect(collectTransferCategories(["csvNullMode", "csvQuoteMode"])).toEqual(["data"]); }); + + it("round-trips zebra row background in data category", () => { + expect(transferCategoryForKey("dataGridZebraRowBg")).toBe("data"); + + const text = serializeSettingsTransfer({ + ...DEFAULT_EDITOR_SETTINGS, + dataGridZebraRowBg: "#232323", + }); + const result = parseSettingsTransferFile(text); + expect(result.ok).toBe(true); + if (!result.ok) return; + expect(result.value.editorSettings.dataGridZebraRowBg).toBe("#232323"); + }); }); diff --git a/apps/desktop/src/lib/settings/editorSettingsDraft.ts b/apps/desktop/src/lib/settings/editorSettingsDraft.ts index 21e22ac3e5..212d7685a1 100644 --- a/apps/desktop/src/lib/settings/editorSettingsDraft.ts +++ b/apps/desktop/src/lib/settings/editorSettingsDraft.ts @@ -76,6 +76,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [ "dataGridCellDetailDialogDefault", "dataGridCrosshairHighlight", "dataGridStripedRows", + "dataGridZebraRowBg", "pageSize", "tableOpenPageSize", "tableOpenSortMode", diff --git a/apps/desktop/src/lib/settings/settingsTransfer.ts b/apps/desktop/src/lib/settings/settingsTransfer.ts index 87603d46ec..e84b13d4f3 100644 --- a/apps/desktop/src/lib/settings/settingsTransfer.ts +++ b/apps/desktop/src/lib/settings/settingsTransfer.ts @@ -169,6 +169,7 @@ const SETTINGS_TRANSFER_CATEGORY_KEYS: Record { } }); + it("defaults zebra row background empty and normalizes custom color", () => { + expect(normalizeEditorSettings({}).dataGridZebraRowBg).toBe(""); + expect(normalizeEditorSettings({ dataGridZebraRowBg: " #334455 \n" }).dataGridZebraRowBg).toBe("#334455"); + expect(normalizeEditorSettings({ dataGridZebraRowBg: null as never }).dataGridZebraRowBg).toBe(""); + }); + it("defaults the data grid font and preserves a custom font family", () => { const defaultFontFamily = `"Geist Variable Tabular", "Geist Variable", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif`; expect(normalizeEditorSettings({}).tableFontFamily).toBe(defaultFontFamily); diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 0d5e16fc16..32a8b39c78 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -989,6 +989,7 @@ export interface EditorSettings { dataGridCellDetailDialogDefault: boolean; dataGridCrosshairHighlight: boolean; dataGridStripedRows: boolean; + dataGridZebraRowBg: string; dataGridMultiRowTranspose: boolean; dataGridHideNullColumns: boolean; dataGridBooleanDisplayMode: "dropdown" | "checkbox"; @@ -1289,6 +1290,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { dataGridCellDetailDialogDefault: false, dataGridCrosshairHighlight: false, dataGridStripedRows: true, + dataGridZebraRowBg: "", dataGridMultiRowTranspose: false, dataGridHideNullColumns: false, dataGridBooleanDisplayMode: "dropdown", @@ -1884,6 +1886,7 @@ export function normalizeEditorSettings(settings: Partial, exist dataGridCellDetailDialogDefault: settings.dataGridCellDetailDialogDefault === true, dataGridCrosshairHighlight: typeof settings.dataGridCrosshairHighlight === "boolean" ? settings.dataGridCrosshairHighlight : DEFAULT_EDITOR_SETTINGS.dataGridCrosshairHighlight, dataGridStripedRows: typeof settings.dataGridStripedRows === "boolean" ? settings.dataGridStripedRows : DEFAULT_EDITOR_SETTINGS.dataGridStripedRows, + dataGridZebraRowBg: typeof settings.dataGridZebraRowBg === "string" ? settings.dataGridZebraRowBg.trim() : DEFAULT_EDITOR_SETTINGS.dataGridZebraRowBg, dataGridMultiRowTranspose: settings.dataGridMultiRowTranspose === true, dataGridHideNullColumns: settings.dataGridHideNullColumns === true, dataGridBooleanDisplayMode: settings.dataGridBooleanDisplayMode === "checkbox" ? "checkbox" : "dropdown", @@ -2743,6 +2746,7 @@ export const useSettingsStore = defineStore("settings", () => { if (partial.dataGridCellDetailDialogDefault !== undefined) editorSettings.value.dataGridCellDetailDialogDefault = partial.dataGridCellDetailDialogDefault === true; if (partial.dataGridCrosshairHighlight !== undefined) editorSettings.value.dataGridCrosshairHighlight = typeof partial.dataGridCrosshairHighlight === "boolean" ? partial.dataGridCrosshairHighlight : DEFAULT_EDITOR_SETTINGS.dataGridCrosshairHighlight; if (partial.dataGridStripedRows !== undefined) editorSettings.value.dataGridStripedRows = typeof partial.dataGridStripedRows === "boolean" ? partial.dataGridStripedRows : DEFAULT_EDITOR_SETTINGS.dataGridStripedRows; + if (partial.dataGridZebraRowBg !== undefined) editorSettings.value.dataGridZebraRowBg = typeof partial.dataGridZebraRowBg === "string" ? partial.dataGridZebraRowBg.trim() : DEFAULT_EDITOR_SETTINGS.dataGridZebraRowBg; if (partial.dataGridMultiRowTranspose !== undefined) editorSettings.value.dataGridMultiRowTranspose = partial.dataGridMultiRowTranspose === true; if (partial.dataGridHideNullColumns !== undefined) editorSettings.value.dataGridHideNullColumns = partial.dataGridHideNullColumns === true; if (partial.dataGridBooleanDisplayMode !== undefined) editorSettings.value.dataGridBooleanDisplayMode = partial.dataGridBooleanDisplayMode === "dropdown" ? "dropdown" : "checkbox";