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

2
packages/manage/router/index.vue

@ -37,7 +37,7 @@ import { useRouter, useRoute } from "vue-router";
import { Api, NoobHead } from "noob-mengyxu"; import { Api, NoobHead } from "noob-mengyxu";
import md5 from "js-md5"; 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 { VITE_APP_VERSION, VITE_GIT_HASH, NODE_ENV } = import.meta.env;
const { Head, MenuTree, HeadPersonal, Fullscreen, StyleChange, LangChange, SizeChange } = NoobHead; const { Head, MenuTree, HeadPersonal, Fullscreen, StyleChange, LangChange, SizeChange } = NoobHead;

Loading…
Cancel
Save