Browse Source

feat: add mini table header rendering for dynamic header height

- Mini table now includes a header row that mirrors real table headers
- Header height is measured from mini table when prop.headerHeight is not set
- resolvedHeaderHeight computed falls back to measured header height
- Both row and header heights measured via ResizeObserver on container
- Update DEV_MODE_TS timestamp

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dev
hechang27-sprt 3 months ago
parent
commit
1fe3718f4b
  1. 72
      packages/base/data/list-table-v2.vue
  2. 2
      packages/manage/router/index.vue

72
packages/base/data/list-table-v2.vue

@ -1,16 +1,18 @@ @@ -1,16 +1,18 @@
<template>
<div class="list-table-v2" :style="containerStyle">
<!-- Mini hidden table for measuring row height (only when using dynamic height mode) -->
<!-- Mini hidden table for measuring row height and header height (only when using dynamic height mode) -->
<!-- This mirrors the real table's cell rendering for accurate height estimation -->
<div v-if="shouldUseProbeRow" ref="miniTableRef" class="mini-table" aria-hidden="true">
<div v-if="miniTableData.length > 0" class="mini-table-inner">
<!-- Mini header row -->
<div ref="miniHeaderRef" class="mini-row mini-header">
<div v-for="item in prop.columns || []" :key="item.key" class="mini-cell mini-header-cell" :style="getMiniCellStyle(item)">
<span class="mini-header-content">{ item.name || (item.i18n ? t(item.i18n) : item.key) }</span>
</div>
</div>
<!-- Mini data rows -->
<div v-for="(row, rowIdx) in miniTableData" :key="rowIdx" class="mini-row">
<div
v-for="item in prop.columns || []"
:key="item.key"
class="mini-cell"
:style="getMiniCellStyle(item)"
>
<div v-for="item in prop.columns || []" :key="item.key" class="mini-cell" :style="getMiniCellStyle(item)">
{renderCellContent(item, lodash.get(row, item.dataKey || item.key), row, slots)}
</div>
</div>
@ -27,7 +29,7 @@ @@ -27,7 +29,7 @@
:width="width"
:height="resolvedHeight(height)"
:row-height="prop.rowHeight"
:header-height="prop.headerHeight"
:header-height="resolvedHeaderHeight"
:estimated-row-height="estimatedRowHeightToUse"
:border="border"
:row-key="rowKey"
@ -68,9 +70,11 @@ const { t } = useI18n(); @@ -68,9 +70,11 @@ const { t } = useI18n();
const { state } = useStore();
const table = ref();
const miniTableRef = ref();
const miniHeaderRef = ref();
const myTableRef = ref(); // Reference to .my-table for ResizeObserver
const miniTableData = ref<any[]>([]);
const estimatedRowHeight = ref<number | undefined>(undefined);
const estimatedHeaderHeight = ref<number | undefined>(undefined);
let miniTableResizeObserver: ResizeObserver | null = null;
// Header height constant
@ -198,7 +202,14 @@ const estimatedRowHeightToUse = computed(() => { @@ -198,7 +202,14 @@ const estimatedRowHeightToUse = computed(() => {
return estimatedRowHeight.value; // use probe-measured value
});
// Measure mini table row height when data changes (only when using probe row)
// The header height to pass to el-table-v2
// Only use measured header height when prop.headerHeight is NOT explicitly set
const resolvedHeaderHeight = computed(() => {
if (prop.headerHeight !== undefined) return prop.headerHeight; // user provided, use it
return estimatedHeaderHeight.value; // use mini-table measured header height
});
// Measure mini table row and header heights when data changes (only when using probe row)
// We use TWO frames delay to let el-table-v2 fully settle before measuring
watch(
() => pageData.value,
@ -214,11 +225,17 @@ watch( @@ -214,11 +225,17 @@ watch(
// Wait TWO frames for layout to settle
await new Promise((resolve) => requestAnimationFrame(resolve));
await new Promise((resolve) => requestAnimationFrame(resolve));
// Measure header height
const headerEl = miniTableRef.value?.querySelector(".mini-header");
const headerHeight = headerEl?.offsetHeight;
if (headerHeight && headerHeight > 0) {
estimatedHeaderHeight.value = headerHeight;
}
// Measure first row height
const firstRow = miniTableRef.value?.querySelector(".mini-row");
const height = firstRow?.offsetHeight;
if (height && height > 0) {
estimatedRowHeight.value = height;
const firstRow = miniTableRef.value?.querySelector(".mini-row:not(.mini-header)");
const rowHeight = firstRow?.offsetHeight;
if (rowHeight && rowHeight > 0) {
estimatedRowHeight.value = rowHeight;
}
},
{ immediate: true }
@ -231,19 +248,26 @@ onMounted(() => { @@ -231,19 +248,26 @@ onMounted(() => {
if (!shouldUseProbeRow.value || miniTableData.value.length === 0) {
return;
}
// Clear previous measurement so el-table-v2 recalculates
// Clear previous measurements so el-table-v2 recalculates
estimatedRowHeight.value = undefined;
estimatedHeaderHeight.value = undefined;
await nextTick();
// Wait TWO frames for layout to settle
await new Promise((resolve) => requestAnimationFrame(resolve));
await new Promise((resolve) => requestAnimationFrame(resolve));
// Measure header height
const headerEl = miniTableRef.value?.querySelector(".mini-header");
const headerHeight = headerEl?.offsetHeight;
if (headerHeight && headerHeight > 0) {
estimatedHeaderHeight.value = headerHeight;
}
// Measure first row height
const firstRow = miniTableRef.value?.querySelector(".mini-row");
const height = firstRow?.offsetHeight;
if (height && height > 0) {
estimatedRowHeight.value = height;
const firstRow = miniTableRef.value?.querySelector(".mini-row:not(.mini-header)");
const rowHeight = firstRow?.offsetHeight;
if (rowHeight && rowHeight > 0) {
estimatedRowHeight.value = rowHeight;
}
}, 100);
}, 50);
// Use ResizeObserver on .my-table to detect width changes
// This is better than window resize because it only fires when OUR container changes
@ -504,6 +528,16 @@ const getMiniCellStyle = (item: TableColumn): Record<string, string> => { @@ -504,6 +528,16 @@ const getMiniCellStyle = (item: TableColumn): Record<string, string> => {
overflow: hidden;
}
.mini-header {
background: v-bind("state.style.tableHeadBg");
font-weight: bold;
}
.mini-header-content {
overflow: hidden;
text-overflow: ellipsis;
}
.mini-cell {
box-sizing: border-box;
overflow: hidden;

2
packages/manage/router/index.vue

@ -37,7 +37,7 @@ import { useRouter, useRoute } from "vue-router"; @@ -37,7 +37,7 @@ import { useRouter, useRoute } from "vue-router";
import { Api, NoobHead } from "noob-mengyxu";
import md5 from "js-md5";
const DEV_MODE_TS = "2026-03-26T08:02:00.000Z";
const DEV_MODE_TS = "2026-03-26T08:16:00.000Z";
const { VITE_APP_VERSION, VITE_GIT_HASH, NODE_ENV } = import.meta.env;
const { Head, MenuTree, HeadPersonal, Fullscreen, StyleChange, LangChange, SizeChange } = NoobHead;

Loading…
Cancel
Save