feat(grid): right-align numeric result columns

This commit is contained in:
gggaiitx authored and GitHub committed 2026-07-27 18:54:54 +08:00
1 parent 66c768061a
commit e2b35944ce
35 files changed
+1082 -109

No files matched your search

+30 -7
View File
@@ -78,7 +78,7 @@ import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/table
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
import { uuid } from "@/lib/common/utils";
import { generateCellValues, type CellValueGenerationKind } from "@/lib/dataGrid/cellValueGeneration";
import { compactHeaderColumnType, resolveHeaderColumnType } from "@/lib/dataGrid/dataGridColumnType";
import { compactHeaderColumnType, isNumericColumnType, resolveHeaderColumnType, resolveResultColumnType } from "@/lib/dataGrid/dataGridColumnType";
import {
canDeleteExistingTdengineRows,
canEditExistingTableRows,
@@ -138,7 +138,7 @@ import { dataGridHeaderContentWidth, scrollbarGutterWidth } from "@/lib/dataGrid
import { canGoNextDataGridPage, hasCompleteLocalDataGridResult, resolveDataGridPaginationTotal } from "@/lib/dataGrid/dataGridPagination";
import { dataGridCountQueryOptions } from "@/lib/dataGrid/dataGridQueryOptions";
import { dataGridBottomScrollTop, dataGridScrollPosition, isDataGridAtScrollBottom, isDataGridNearScrollBottom, shouldCheckInfiniteScrollAfterScroll, type DataGridScrollPosition } from "@/lib/dataGrid/dataGridInfiniteScroll";
import { CANVAS_DATA_GRID_ROW_HEIGHT, dataGridSearchMatchKey, drawCanvasDataGrid } from "@/lib/dataGrid/canvasDataGridRenderer";
import { CANVAS_DATA_GRID_ROW_HEIGHT, canvasDataGridActionReservedWidth, dataGridSearchMatchKey, drawCanvasDataGrid } from "@/lib/dataGrid/canvasDataGridRenderer";
import { DATA_GRID_DARK_STRIPED_ROW_BG, DATA_GRID_LIGHT_STRIPED_ROW_BG } from "@/lib/dataGrid/dataGridPaintTheme";
import { createRowLowerTextCache } from "@/lib/dataGrid/dataGridRowLowerText";
import { dataGridPreviewLabelKey, dataGridSaveActionMode, dataGridSaveToolbarState } from "@/lib/dataGrid/dataGridSaveUi";
@@ -1757,14 +1757,23 @@ const tableColumnTypesByName = computed(() => {
return map;
});
const visibleColumnTypes = computed(() =>
visibleColumnIndexes.value.map((index) => {
const resultColumn = props.result.columns[index]?.toLocaleLowerCase();
const sourceColumn = props.sourceColumns?.[index]?.toLocaleLowerCase();
return (sourceColumn ? tableColumnTypesByName.value.get(sourceColumn) : undefined) || (resultColumn ? tableColumnTypesByName.value.get(resultColumn) : undefined) || props.result.column_types?.[index];
}),
visibleColumnIndexes.value.map((index) =>
resolveResultColumnType({
resultColumnType: props.result.column_types?.[index],
resultColumnName: props.result.columns[index]?.toLocaleLowerCase(),
sourceColumnName: props.sourceColumns?.[index]?.toLocaleLowerCase(),
tableColumnTypesByName: tableColumnTypesByName.value,
}),
),
);
const visibleColumnCount = computed(() => visibleColumnIndexes.value.length);
const numericColumnRightAlign = computed(() => (settingsStore.editorSettings.numericColumnRightAlign ?? true) && !showTranspose.value);
const columnAligns = computed<("left" | "right")[]>(() => {
if (!numericColumnRightAlign.value) return [];
return visibleColumnTypes.value.map((type) => (isNumericColumnType(type) ? "right" : "left"));
});
/** Preview actions from the result preview registry for the current result. */
const previewActions = computed(() => {
if (!props.result) return [];
@@ -4952,6 +4961,16 @@ const canvasDetailButtonStyle = computed(() => {
};
});
const canvasRightAlignedActionCell = computed(() => {
const cell = canvasDetailButtonCell.value;
if (!cell || columnAligns.value[cell.visibleColIdx] !== "right") return null;
return {
rowIndex: cell.rowIndex,
visibleColIdx: cell.visibleColIdx,
reservedWidth: canvasDataGridActionReservedWidth(cell.canQuickDownload),
};
});
function drawCanvasGrid() {
const canvas = canvasRef.value;
const scroller = canvasScrollerElement();
@@ -4988,6 +5007,8 @@ function drawCanvasGrid() {
pageSize: pageSize.value,
currentPage: currentPage.value,
frozenColumnCount: frozenColumnCount.value,
columnAligns: columnAligns.value,
rightAlignedActionCell: canvasRightAlignedActionCell.value,
});
}
@@ -5001,6 +5022,7 @@ watch(
{ immediate: true },
);
watch(showDataGridTopbar, () => nextTick(observeDataGridTopbarWidth), { immediate: true });
watch(columnAligns, () => scheduleCanvasDraw());
watch(
[
displayRowRefs,
@@ -8630,6 +8652,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
:class="{
'data-grid-cell--frozen': col.visibleColIdx < frozenColumnCount,
'data-grid-cell--frozen-separator': frozenColumnCount > 0 && col.visibleColIdx === frozenColumnCount - 1,
'text-right': columnAligns[col.visibleColIdx] === 'right',
'text-muted-foreground italic': isNull(item.data[col.actualColIdx]),
'bg-yellow-500/10 cell-dirty': item.isDirtyCol[col.actualColIdx],
'cell-selected': cellIsSelected(item.displayIndex, col.visibleColIdx) && !item.isDirtyCol[col.actualColIdx],
@@ -5,7 +5,7 @@ import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
import { canReloadUnavailableDataTab } from "@/lib/table/tableDataRefresh";
import type { CSSProperties } from "vue";
import { useI18n } from "vue-i18n";
import { Check, Columns3, Columns3Cog, EyeOff, Loader2, Search, TableProperties, ChevronDown, ChevronUp, Inbox, RefreshCcw, Wrench, Toolbox, Database, Download, Upload, X, Pin, Rows3, SquareDashed, Minus, Plus, ShieldAlert, PanelsTopLeft } from "@lucide/vue";
import { Check, Columns3, Columns3Cog, EyeOff, Loader2, Search, TableProperties, ChevronDown, ChevronUp, Inbox, RefreshCcw, Wrench, Toolbox, Database, Download, Upload, X, Pin, Rows3, SquareDashed, Minus, Plus, ShieldAlert, AlignLeft, AlignRight, PanelsTopLeft } from "@lucide/vue";
import { Splitpanes, Pane } from "splitpanes";
import "splitpanes/dist/splitpanes.css";
import { Button } from "@/components/ui/button";
@@ -264,6 +264,12 @@ function setTableFontSize(value: number) {
settingsStore.updateEditorSettings({ tableFontSize: value });
}
const numericColumnRightAlign = computed(() => settingsStore.editorSettings.numericColumnRightAlign ?? true);
function setNumericColumnRightAlign(value: boolean) {
settingsStore.updateEditorSettings({ numericColumnRightAlign: value });
}
function decreaseTableFontSize() {
setTableFontSize(tableFontSize.value - 1);
}
@@ -1180,6 +1186,30 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
</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">
<component :is="numericColumnRightAlign ? AlignRight : AlignLeft" class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span>{{ t("grid.numericColumnAlign") }}</span>
</div>
<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="!numericColumnRightAlign ? 'bg-background font-semibold text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'"
@click="setNumericColumnRightAlign(false)"
>
{{ t("grid.numericColumnAlignLeft") }}
</button>
<button
type="button"
class="h-5 min-w-0 truncate whitespace-nowrap rounded-[5px] px-2 text-xs transition-colors"
:class="numericColumnRightAlign ? 'bg-background font-semibold text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'"
@click="setNumericColumnRightAlign(true)"
>
{{ t("grid.numericColumnAlignRight") }}
</button>
</div>
</div>
<div class="flex items-center justify-between gap-3 px-3 py-1.5 text-xs" :class="{ 'opacity-60': !dataGridRef?.canToggleAllNullColumns }">
<span class="min-w-0 flex items-center gap-2 font-medium">
<EyeOff class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
@@ -1597,6 +1627,30 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
</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">
<component :is="numericColumnRightAlign ? AlignRight : AlignLeft" class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span>{{ t("grid.numericColumnAlign") }}</span>
</div>
<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="!numericColumnRightAlign ? 'bg-background font-semibold text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'"
@click="setNumericColumnRightAlign(false)"
>
{{ t("grid.numericColumnAlignLeft") }}
</button>
<button
type="button"
class="h-5 min-w-0 truncate whitespace-nowrap rounded-[5px] px-2 text-xs transition-colors"
:class="numericColumnRightAlign ? 'bg-background font-semibold text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'"
@click="setNumericColumnRightAlign(true)"
>
{{ t("grid.numericColumnAlignRight") }}
</button>
</div>
</div>
<div class="flex items-center justify-between gap-3 px-3 py-1.5 text-xs" :class="{ 'opacity-60': !dataGridRef?.canToggleAllNullColumns }">
<span class="min-w-0 flex items-center gap-2 font-medium">
<EyeOff class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />