|
|
|
|
@ -4,8 +4,17 @@
@@ -4,8 +4,17 @@
|
|
|
|
|
<div v-if="debug" class="debug-info"> |
|
|
|
|
<div>Container Width: {{ containerWidth }}</div> |
|
|
|
|
<div>Total Height: {{ virtualTotalHeight }}</div> |
|
|
|
|
<div>Visible Rows: {{ visibleRows.length }}</div> |
|
|
|
|
<div>Visible Rows: {{ visibleRows.map((entry) => entry.height).join(",") }}</div> |
|
|
|
|
<div> |
|
|
|
|
Cell Heights: |
|
|
|
|
{{ |
|
|
|
|
rowHeights.map((entry) => |
|
|
|
|
entry.cellHeights?.map((entry) => `${entry.isCustomRenderer ? "*" : ""}${entry.height}`).join(",") |
|
|
|
|
) |
|
|
|
|
}} |
|
|
|
|
</div> |
|
|
|
|
<div>Total Rows: {{ pageData.length }}</div> |
|
|
|
|
<div>Column widths: {{ columnWidths.map((w) => Math.round(w)).join(",") }}</div> |
|
|
|
|
<div> |
|
|
|
|
Column configs: |
|
|
|
|
{{ |
|
|
|
|
@ -86,6 +95,8 @@ import { usePretextColumnWidths, type ColumnFlexConfig } from "./usePretextColum
@@ -86,6 +95,8 @@ import { usePretextColumnWidths, type ColumnFlexConfig } from "./usePretextColum
|
|
|
|
|
import { usePretextRowHeights, type RowHeightEntry } from "./usePretextRowHeights"; |
|
|
|
|
import { useVirtualRows } from "./useVirtualRows"; |
|
|
|
|
|
|
|
|
|
const DEFAULT_FONT = "14px sans-serif"; |
|
|
|
|
|
|
|
|
|
const slots = useSlots(); |
|
|
|
|
const { t } = useI18n(); |
|
|
|
|
const { state } = useStore(); |
|
|
|
|
@ -119,6 +130,7 @@ interface Props {
@@ -119,6 +130,7 @@ interface Props {
|
|
|
|
|
rowHeight?: number; |
|
|
|
|
estimatedRowHeight?: number; |
|
|
|
|
headerHeight?: number; |
|
|
|
|
font?: string; |
|
|
|
|
debug?: boolean; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -139,6 +151,7 @@ const props = withDefaults(defineProps<Props>(), {
@@ -139,6 +151,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
|
|
rowHeight: undefined, |
|
|
|
|
estimatedRowHeight: undefined, |
|
|
|
|
headerHeight: 44, |
|
|
|
|
font: DEFAULT_FONT, |
|
|
|
|
debug: false, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
@ -173,26 +186,88 @@ const columnsRef = toRef(props, "columns");
@@ -173,26 +186,88 @@ const columnsRef = toRef(props, "columns");
|
|
|
|
|
// Pass computed pageData to hooks |
|
|
|
|
const pageDataRef = toRef(pageData); |
|
|
|
|
|
|
|
|
|
// ============================================================================= |
|
|
|
|
// Cell formatting |
|
|
|
|
// ============================================================================= |
|
|
|
|
const getHeaderText = (col: ListTableColumn<T>): string => { |
|
|
|
|
if (col.name) return col.name; |
|
|
|
|
if (col.i18n) return t(col.i18n); |
|
|
|
|
return col.key; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const formatCellValue = (row: T, col: ListTableColumn<T>): string => { |
|
|
|
|
const value = (row as any)[col.dataKey || col.key]; |
|
|
|
|
|
|
|
|
|
if (col.dict) { |
|
|
|
|
return formatterByDist(col.dict, value); |
|
|
|
|
} |
|
|
|
|
if (col.timestamp) { |
|
|
|
|
return formatStamp(value); |
|
|
|
|
} |
|
|
|
|
if (col.filesize) { |
|
|
|
|
return formatFileSize(value); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (value === undefined || value === null || value === "") { |
|
|
|
|
return "--"; |
|
|
|
|
} |
|
|
|
|
return String(value); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const getValue = (value: any): string => { |
|
|
|
|
if ((typeof value === "undefined" || value === null || value === "") && value !== 0) { |
|
|
|
|
return "--"; |
|
|
|
|
} |
|
|
|
|
return value; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const formatStamp = (value: any): string => { |
|
|
|
|
const date = new Date(value * 1000); |
|
|
|
|
const month = date.getMonth() < 9 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1; |
|
|
|
|
const day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate(); |
|
|
|
|
const hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(); |
|
|
|
|
const minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(); |
|
|
|
|
const second = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds(); |
|
|
|
|
return `${date.getFullYear()}-${month}-${day} ${hour}:${minute}:${second}`; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const formatFileSize = (value: any): string => { |
|
|
|
|
const k = value / 1024; |
|
|
|
|
if (k < 1) return value + "B"; |
|
|
|
|
const m = k / 1024; |
|
|
|
|
if (m < 1) return k.toFixed(2) + "K"; |
|
|
|
|
const g = m / 1024; |
|
|
|
|
if (g < 1) return m.toFixed(2) + "M"; |
|
|
|
|
return g.toFixed(2) + "G"; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const formatterByDist = (dictKey: string, cellData: any): string => { |
|
|
|
|
if (!dictKey) return getValue(cellData); |
|
|
|
|
const mapping = (state as any).dict[dictKey]; |
|
|
|
|
if (mapping == null) return getValue(cellData); |
|
|
|
|
return mapping[cellData] == null ? cellData : mapping[cellData]; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// ============================================================================= |
|
|
|
|
// Wire hooks |
|
|
|
|
// ============================================================================= |
|
|
|
|
|
|
|
|
|
// Column width computation via pretext |
|
|
|
|
const { computedConfigs, totalFlexBasis } = usePretextColumnWidths(pageDataRef, columnsRef, containerWidth, { |
|
|
|
|
const { computedConfigs, totalFlexBasis, columnWidths } = usePretextColumnWidths( |
|
|
|
|
pageDataRef, |
|
|
|
|
columnsRef, |
|
|
|
|
containerWidth, |
|
|
|
|
{ |
|
|
|
|
font: "14px Inter, sans-serif", |
|
|
|
|
headerFont: "bold 14px Inter, sans-serif", |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// Column widths as array of numbers (for usePretextRowHeights) |
|
|
|
|
const columnWidths = computed<number[]>(() => { |
|
|
|
|
return computedConfigs.value.map((c) => c.flexBasis); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// Row heights via pretext |
|
|
|
|
const { rowHeights, totalHeight } = usePretextRowHeights(pageDataRef, columnsRef, columnWidths, { |
|
|
|
|
font: "14px Inter, sans-serif", |
|
|
|
|
// Row heights via pretext - use actual column widths from flexbox algorithm |
|
|
|
|
const { rowHeights, totalHeight } = usePretextRowHeights(pageDataRef, columnsRef, columnWidths, formatCellValue, { |
|
|
|
|
font: props.font, |
|
|
|
|
lineHeight: 20, |
|
|
|
|
rowPadding: 12, |
|
|
|
|
debug: props.debug, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// Virtualizer (needs rowHeights as number[] and viewportHeight) |
|
|
|
|
@ -237,6 +312,12 @@ const tableContainerStyle = computed(() => {
@@ -237,6 +312,12 @@ const tableContainerStyle = computed(() => {
|
|
|
|
|
// Column styling |
|
|
|
|
// ============================================================================= |
|
|
|
|
function getColumnStyle(col: ListTableColumn<T>): Record<string, string> { |
|
|
|
|
// Find the column index to get the computed width |
|
|
|
|
const colIndex = columnsRef.value.findIndex((c) => c.key === col.key); |
|
|
|
|
const computedWidth = colIndex >= 0 ? columnWidths.value[colIndex] : 0; |
|
|
|
|
|
|
|
|
|
if (!computedWidth || computedWidth <= 0) { |
|
|
|
|
// Fallback to flex property if no computed width |
|
|
|
|
const config = computedConfigs.value.find((c) => c.key === col.key); |
|
|
|
|
if (!config) { |
|
|
|
|
return { flex: "1 1 100px", minWidth: "50px", maxWidth: "300px" }; |
|
|
|
|
@ -248,6 +329,14 @@ function getColumnStyle(col: ListTableColumn<T>): Record<string, string> {
@@ -248,6 +329,14 @@ function getColumnStyle(col: ListTableColumn<T>): Record<string, string> {
|
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Use the yoga-computed width directly for consistent measurement |
|
|
|
|
return { |
|
|
|
|
width: `${computedWidth}px`, |
|
|
|
|
minWidth: `${computedWidth}px`, |
|
|
|
|
maxWidth: `${computedWidth}px`, |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ============================================================================= |
|
|
|
|
// Header height resolution |
|
|
|
|
// ============================================================================= |
|
|
|
|
@ -255,68 +344,6 @@ const resolvedHeaderHeight = computed(() => {
@@ -255,68 +344,6 @@ const resolvedHeaderHeight = computed(() => {
|
|
|
|
|
return props.headerHeight ?? 44; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// ============================================================================= |
|
|
|
|
// Cell formatting |
|
|
|
|
// ============================================================================= |
|
|
|
|
const getHeaderText = (col: ListTableColumn<T>): string => { |
|
|
|
|
if (col.name) return col.name; |
|
|
|
|
if (col.i18n) return t(col.i18n); |
|
|
|
|
return col.key; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const formatCellValue = (row: T, col: ListTableColumn<T>): string => { |
|
|
|
|
const value = (row as any)[col.dataKey || col.key]; |
|
|
|
|
|
|
|
|
|
if (col.dict) { |
|
|
|
|
return formatterByDist(col.dict, value); |
|
|
|
|
} |
|
|
|
|
if (col.timestamp) { |
|
|
|
|
return formatStamp(value); |
|
|
|
|
} |
|
|
|
|
if (col.filesize) { |
|
|
|
|
return formatFileSize(value); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (value === undefined || value === null || value === "") { |
|
|
|
|
return "--"; |
|
|
|
|
} |
|
|
|
|
return String(value); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const getValue = (value: any): string => { |
|
|
|
|
if ((typeof value === "undefined" || value === null || value === "") && value !== 0) { |
|
|
|
|
return "--"; |
|
|
|
|
} |
|
|
|
|
return value; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const formatStamp = (value: any): string => { |
|
|
|
|
const date = new Date(value * 1000); |
|
|
|
|
const month = date.getMonth() < 9 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1; |
|
|
|
|
const day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate(); |
|
|
|
|
const hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(); |
|
|
|
|
const minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(); |
|
|
|
|
const second = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds(); |
|
|
|
|
return `${date.getFullYear()}-${month}-${day} ${hour}:${minute}:${second}`; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const formatFileSize = (value: any): string => { |
|
|
|
|
const k = value / 1024; |
|
|
|
|
if (k < 1) return value + "B"; |
|
|
|
|
const m = k / 1024; |
|
|
|
|
if (m < 1) return k.toFixed(2) + "K"; |
|
|
|
|
const g = m / 1024; |
|
|
|
|
if (g < 1) return m.toFixed(2) + "M"; |
|
|
|
|
return g.toFixed(2) + "G"; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const formatterByDist = (dictKey: string, cellData: any): string => { |
|
|
|
|
if (!dictKey) return getValue(cellData); |
|
|
|
|
const mapping = (state as any).dict[dictKey]; |
|
|
|
|
if (mapping == null) return getValue(cellData); |
|
|
|
|
return mapping[cellData] == null ? cellData : mapping[cellData]; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// ============================================================================= |
|
|
|
|
// Scroll handling |
|
|
|
|
// ============================================================================= |
|
|
|
|
@ -499,7 +526,7 @@ onUnmounted(() => {
@@ -499,7 +526,7 @@ onUnmounted(() => {
|
|
|
|
|
white-space: normal; |
|
|
|
|
word-break: break-word; |
|
|
|
|
color: v-bind("state.style.tableColor"); |
|
|
|
|
font-size: var(--el-font-size-base); |
|
|
|
|
font: v-bind("props.font"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.my-pagination { |
|
|
|
|
|