feat: auto-fit column widths based on header and data content

Scan column names and first 50 rows to calculate optimal column width
instead of using a fixed 150px default. Double-click column separator
to auto-fit individual columns. Min 60px, max 400px.
This commit is contained in:
t8y2
2026-05-09 00:51:51 +08:00
parent 9ea098bd57
commit ad79642502
2 changed files with 44 additions and 2 deletions
+43 -1
View File
@@ -361,9 +361,33 @@ const gridRef = ref<HTMLDivElement>();
const headerRef = ref<HTMLDivElement>();
const { width: gridWidth } = useElementSize(gridRef);
const COL_MIN_WIDTH = 60;
const COL_MAX_WIDTH = 400;
const COL_CHAR_WIDTH = 8;
const COL_HEADER_PADDING = 48;
const COL_CELL_PADDING = 28;
const COL_SAMPLE_ROWS = 50;
function estimateTextWidth(text: string, padding: number): number {
return text.length * COL_CHAR_WIDTH + padding;
}
function initColumnWidths() {
if (columnWidths.value.length !== props.result.columns.length) {
columnWidths.value = props.result.columns.map(() => 150);
const rows = props.result.rows;
const sampleCount = Math.min(rows.length, COL_SAMPLE_ROWS);
columnWidths.value = props.result.columns.map((colName, colIdx) => {
let maxWidth = estimateTextWidth(colName, COL_HEADER_PADDING);
for (let i = 0; i < sampleCount; i++) {
const val = rows[i]?.[colIdx];
if (val == null) continue;
const text = typeof val === "object" ? JSON.stringify(val) : String(val);
const displayLen = Math.min(text.length, 60);
const w = displayLen * COL_CHAR_WIDTH + COL_CELL_PADDING;
if (w > maxWidth) maxWidth = w;
}
return Math.max(COL_MIN_WIDTH, Math.min(COL_MAX_WIDTH, Math.round(maxWidth)));
});
}
}
@@ -394,6 +418,23 @@ function onResizeStart(colIdx: number, event: MouseEvent) {
document.addEventListener("mouseup", onUp);
}
function autoFitColumn(colIdx: number) {
const colName = props.result.columns[colIdx];
if (!colName) return;
const rows = props.result.rows;
const sampleCount = Math.min(rows.length, COL_SAMPLE_ROWS);
let maxWidth = estimateTextWidth(colName, COL_HEADER_PADDING);
for (let i = 0; i < sampleCount; i++) {
const val = rows[i]?.[colIdx];
if (val == null) continue;
const text = typeof val === "object" ? JSON.stringify(val) : String(val);
const displayLen = Math.min(text.length, 60);
const w = displayLen * COL_CHAR_WIDTH + COL_CELL_PADDING;
if (w > maxWidth) maxWidth = w;
}
columnWidths.value[colIdx] = Math.max(COL_MIN_WIDTH, Math.min(COL_MAX_WIDTH, Math.round(maxWidth)));
}
const ROW_NUM_WIDTH = 48;
const baseTotalWidth = computed(() => columnWidths.value.reduce((a, b) => a + b, 0));
const renderedColumnWidths = computed(() => {
@@ -1552,6 +1593,7 @@ defineExpose({
<div
class="absolute right-0 top-0 bottom-0 w-1.5 cursor-col-resize hover:bg-primary/30"
@mousedown.stop="onResizeStart(colIdx, $event)"
@dblclick.stop="autoFitColumn(colIdx)"
/>
</div>
</TooltipTrigger>
+1 -1
View File
@@ -12,7 +12,7 @@ import {
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
import { useQueryStore } from "@/stores/queryStore";
import { useTabScroll } from "@/composables/useTabScroll";
import { connectionColor, tabDisplayTitle, tabTooltipLines } from "@/lib/tabPresentation";
import { tabDisplayTitle, tabTooltipLines } from "@/lib/tabPresentation";
const { t } = useI18n();
const queryStore = useQueryStore();