Browse Source

chore: remove old list-table-v2 implementation

dev
hechang27-sprt 2 months ago
parent
commit
a949d2f2be
  1. 95
      .trellis/workspace/hechang27-sprt/04-10-code-review.md
  2. 2
      AGENTS.md
  3. 2
      CLAUDE.md
  4. 831
      packages/base/data/list-table-v2.vue

95
.trellis/workspace/hechang27-sprt/04-10-code-review.md

@ -0,0 +1,95 @@ @@ -0,0 +1,95 @@
# Code review
Found 4 urgent issues need to be fixed:
## 1 Dimension props regressed, so height/maxHeight no longer follow the old contract
FilePath: packages/base/data/list-table-v2/list-table-v2.vue:291 line 291
if (props.height !== undefined) {
const h = String(props.height);
style.maxHeight = h.endsWith("px") ? h : `${h}px`;
}
// props.maxHeight handling is commented out
height is being applied as maxHeight, maxHeight is ignored entirely, and minHeight is unused. The old component and the
demo page both rely on these props behaving distinctly, so PR2 currently breaks the documented sizing behavior.
### Suggested fix
Restore the old container contract: set style.height from props.height, apply style.maxHeight from props.maxHeight, honor
props.minHeight, and keep viewportHeight derived from the actual constrained container size.
———
## 2 rowKey is exposed but never used, so virtual rows are keyed by visible index
FilePath: packages/base/data/list-table-v2/list-table-v2.vue:32 line 32
<div
v-for="row in visibleRows"
:key="row.index"
class="table-row"
This throws away stable row identity. On page changes, filtering, or reordered data, Vue will reuse row subtrees by index
instead of by record identity, which is exactly what rowKey is supposed to prevent.
### Suggested fix
Derive a stable key from the actual row, for example getRow(row.index)?.[props.rowKey] ?? row.index, and use that for the
row wrapper.
———
## 3 Pretext width calculation is measuring different text/fonts than the component actually renders
FilePath: packages/base/data/list-table-v2/usePretextColumnWidths.ts:198 line 198
const font = options?.font ?? DEFAULT_FONT;
const headerFont = options?.headerFont ?? DEFAULT_HEADER_FONT;
...
const headerText = col.name || col.i18n || colKey;
The component renders body text with props.font and now defaults to 14px Microsoft YaHei, but list-table-v2.vue still
calls this hook with hardcoded Inter fonts. On top of that, header sizing uses the raw i18n key instead of the translated
label. That means the measured widths diverge from the rendered widths, which directly undermines PR2’s “column widths
adapt to content” acceptance criterion.
### Suggested fix
Pass the real rendered font into usePretextColumnWidths from packages/base/data/list-table-v2/list-table-v2.vue:241,
derive the header font from that same base font, and measure the translated header text instead of col.i18n.
———
## 4 The rewrite dropped timestampFormat from the public API
FilePath: packages/base/data/list-table-v2/list-table-v2.vue:110 line 110
interface Props {
...
border?: boolean;
rowHeight?: number;
estimatedRowHeight?: number;
headerHeight?: number;
font?: string;
debug?: boolean;
}
The old component exposed timestampFormat, but the new runtime props and exported ListTableProps no longer include it,
while timestamp rendering is still part of the component contract. That is a backward-compatibility break against the PRD
goal of keeping the existing prop interface.
### Suggested fix
Re-add timestampFormat to both the runtime props and packages/base/data/list-table-v2/types.ts:87, then thread it into the
timestamp formatting path so existing consumers keep their display override.
———
Would you like me to use the Suggested fix section to address these issues?
# Response
› 4 is intentional, 1/2/3 should be fixed. However, there is an additional problem: when using custom slots like the table
11 in the demo page, the inner elements consistently overflows the table cell even with overflow: hidden...

2
AGENTS.md

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **noob-components** (1086 symbols, 2384 relationships, 85 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **noob-components** (1066 symbols, 2354 relationships, 84 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

2
CLAUDE.md

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **noob-components** (1086 symbols, 2384 relationships, 85 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **noob-components** (1066 symbols, 2354 relationships, 84 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

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

@ -1,831 +0,0 @@ @@ -1,831 +0,0 @@
<template>
<div class="list-table-v2" :style="containerStyle">
<!-- 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 -->
<!-- Hidden via display:none when off-screen to save GPU/CPU -->
<div
v-if="shouldUseProbeRow"
ref="miniTableRef"
class="mini-table"
:class="{ 'is-hidden': !isInViewport }"
aria-hidden="true"
>
<div class="mini-table-inner">
<!-- Mini header row -->
<div ref="miniHeaderRef" class="mini-row mini-header">
<div
v-for="(column, columnIndex) in tableColumns || []"
:key="column.key"
class="mini-cell mini-header-cell"
:style="getMiniCellStyle(column)"
>
<MiniTableHeader :cellData="undefined as T" :columnIndex :columns="tableColumns" :column />
</div>
</div>
<!-- Mini data rows -->
<div v-if="miniTableData.length > 0" ref="miniTableRowsRef" class="mini-table-rows">
<div v-for="(rowData, rowIndex) in miniTableData" :key="rowIndex" class="mini-row">
<div
v-for="(column, columnIndex) in tableColumns || []"
:key="column.key"
class="mini-cell"
:style="getMiniCellStyle(column)"
>
<MiniTableCell
:cellData="lodash.get(rowData, String(column.dataKey || column.key))"
:columnIndex
:columns="tableColumns"
:column
:rowData
:rowIndex
/>
</div>
</div>
</div>
</div>
</div>
<div v-if="debug" class="debug-info">
<div>Estimated Row Height: {{ resolvedRowHeight }}</div>
<div>Estimated Header Height: {{ resolvedHeaderHeight }}</div>
</div>
<div ref="myTableRef" class="my-table">
<el-auto-resizer>
<template #default="{ height, width }">
<el-table-v2
ref="table"
:columns="tableColumns"
:data="pageData"
:width="width"
:height="resolvedHeight(height)"
:row-height="prop.rowHeight"
:header-height="resolvedHeaderHeight"
:estimated-row-height="resolvedRowHeight"
:border="border"
:row-key="rowKey"
:fixed="hasFixedColumns"
:class="border ? 'has-border' : ''"
@scroll="onScroll"
@row-click="onRowClick"
@cell-click="onCellClick"
/>
</template>
</el-auto-resizer>
</div>
<div v-if="page" class="my-pagination">
<el-pagination
:small="state.size.size == 'small'"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="example.page"
:page-sizes="[10, 20, 50, 100, 200]"
:page-size="example.size"
layout="total, sizes, prev, pager, next, jumper"
:total="data.total"
/>
</div>
</div>
</template>
<script lang="tsx" setup generic="T">
import { useStore } from "vuex";
import {
ref,
computed,
watch,
h,
onMounted,
onUpdated,
onUnmounted,
useSlots,
renderSlot,
nextTick,
VNode,
StyleValue,
useTemplateRef,
} from "vue";
import { useI18n } from "vue3-i18n";
import { Column, ElAutoResizer, ElTableV2 } from "element-plus";
import * as lodash from "lodash-es";
import TzDateTime from "../item/tzDateTime.vue";
import {
CellRenderer,
CellRendererParams,
HeaderCellRenderer,
HeaderCellRendererParams,
} from "element-plus/es/components/table-v2/src/types.mjs";
import { match, P } from "ts-pattern";
import { FixedDir } from "element-plus/es/components/table-v2/src/constants.mjs";
const slots = useSlots();
const { t } = useI18n();
const { state } = useStore();
const miniTableRef = useTemplateRef("miniTableRef");
const miniHeaderRef = useTemplateRef("miniHeaderRef");
const miniTableRowsRef = useTemplateRef("miniTableRowsRef");
const myTableRef = useTemplateRef("myTableRef"); // 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;
let lastMiniTableHeight = 0;
// Track if table is visible in viewport - only probe when visible
const isInViewport = ref(false);
let viewportObserver: IntersectionObserver | null = null;
// Header height constant
type TimestampValue =
| undefined
| boolean
| string
| {
valueFormat?: string;
valueTz?: string;
displayFormat?: string;
locale?: string;
type?: "iso8601" | "unix" | "unixMillis";
};
interface TzDateTimeConfig {
valueFormat?: string;
valueTz?: string;
displayFormat?: string;
locale?: string;
type?: "iso8601" | "unix" | "unixMillis";
}
interface ListTableColumn<T> {
key: string;
dataKey?: string;
name?: string;
i18n?: string;
type?: string;
width?: string | number;
minWidth?: string | number;
fixed?: boolean | "left" | "right";
align?: "left" | "center" | "right";
slot?: boolean;
dict?: string;
timestamp?: TimestampValue;
filesize?: boolean;
// Custom renderers - JSX-returning functions that override slot/default rendering
cellRenderer?: CellRenderer<T>;
headerCellRenderer?: HeaderCellRenderer<T>;
[others: string]: any;
}
interface Props {
data?: any;
columns?: ListTableColumn<T>[];
page?: boolean;
height?: number | string;
minHeight?: number | string;
maxHeight?: number | string;
example?: any;
rowKey?: string;
selectKey?: string;
treeProps?: any;
lazy?: boolean;
border?: boolean;
timestampFormat?: string;
rowHeight?: number;
estimatedRowHeight?: number;
headerHeight?: number;
debug?: boolean;
}
const prop = withDefaults(defineProps<Props>(), {
data: () => [],
columns: () => [],
page: false,
height: undefined,
minHeight: 300,
maxHeight: undefined,
example: () => ({}),
rowKey: "id",
selectKey: undefined,
treeProps: undefined,
lazy: false,
border: false,
timestampFormat: "YYYY-MM-DD HH:mm:ss",
rowHeight: undefined,
estimatedRowHeight: undefined,
headerHeight: undefined,
});
const emit = defineEmits(["query", "selection-change", "row-click", "cell-click"]);
// Pagination data - use data.data if page response, otherwise data array
const pageData = computed(() => {
if (!prop.data) return [];
if (prop.page && prop.data.data) {
return prop.data.data;
}
if (Array.isArray(prop.data)) {
return prop.data;
}
return [];
});
// Whether to use probe row for dynamic height measurement
// Only use probe row when rowHeight is NOT explicitly specified
const shouldUseProbeRow = computed(() => {
return prop.rowHeight === undefined && prop.estimatedRowHeight === undefined;
});
// Check if any column is fixed - used to set table's fixed prop
const hasFixedColumns = computed(() => {
return prop.columns.some((col) => col.fixed === "left" || col.fixed === "right" || col.fixed === true);
});
// The estimated row height to pass to el-table-v2
// Only used when rowHeight is NOT set (dynamic height mode)
const resolvedRowHeight = computed(() => {
if (prop.rowHeight !== undefined) return undefined; // fixed height mode, don't use estimated
if (prop.estimatedRowHeight !== undefined) return prop.estimatedRowHeight; // user provided estimate
return estimatedRowHeight.value; // use probe-measured value
});
// 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,
async (data) => {
// Skip if rowHeight is set (fixed height mode) or no data
if (!shouldUseProbeRow.value || !data || data.length === 0) {
return;
}
// Use up to 3 rows for mini table
miniTableData.value = lodash.sampleSize(data, 3);
// Wait for mini table to render
await nextTick();
// Wait TWO frames for layout to settle
await new Promise((resolve) => requestAnimationFrame(resolve));
await new Promise((resolve) => requestAnimationFrame(resolve));
const { headerHeight, rowHeight } = estimateTableRowHeight();
if (headerHeight && headerHeight > 0) {
estimatedHeaderHeight.value = headerHeight;
}
if (rowHeight && rowHeight > 0) {
estimatedRowHeight.value = rowHeight;
}
// Update last known mini-table height for ResizeObserver comparison
lastMiniTableHeight = miniTableRef.value?.offsetHeight || 0;
},
{ immediate: true }
);
function estimateTableRowHeight() {
const headerHeight = miniHeaderRef.value?.offsetHeight;
const tableRowsHeight = miniTableRowsRef.value?.offsetHeight;
const rowHeight =
tableRowsHeight && tableRowsHeight > 0 && miniTableData.value.length > 0
? lodash.round(tableRowsHeight / miniTableData.value.length)
: undefined;
return { headerHeight, rowHeight };
}
// Setup ResizeObserver on mount
// Observe miniTableRef instead of myTableRef - only re-probe if mini-table's rendered height changes
// This avoids unnecessary re-probing during window resize when container width changes but row heights stay same
onMounted(() => {
// Resize handler - only re-probe if the mini-table's actual height changed AND table is visible
const handleResize = lodash.debounce(async () => {
// Skip if not using probe, no data, or not visible in viewport
if (!shouldUseProbeRow.value || miniTableData.value.length === 0 || !isInViewport.value) {
return;
}
// Check if mini-table height actually changed (e.g., due to text wrapping)
const currentHeight = miniTableRef.value?.offsetHeight || 0;
if (currentHeight === lastMiniTableHeight) {
return; // Height unchanged, no need to re-probe
}
lastMiniTableHeight = currentHeight;
await nextTick();
await new Promise((resolve) => requestAnimationFrame(resolve));
await new Promise((resolve) => requestAnimationFrame(resolve));
// Measure first while old values still set
const { headerHeight, rowHeight } = estimateTableRowHeight();
const newHeader = headerHeight && headerHeight > 0 ? headerHeight : estimatedHeaderHeight.value;
const newRow = rowHeight && rowHeight > 0 ? rowHeight : estimatedRowHeight.value;
// Clear then set in same microtask to minimize gap
estimatedRowHeight.value = undefined;
estimatedHeaderHeight.value = undefined;
queueMicrotask(() => {
estimatedRowHeight.value = newRow;
estimatedHeaderHeight.value = newHeader;
});
}, 50);
// ResizeObserver for mini-table height changes
miniTableResizeObserver = new ResizeObserver(() => {
handleResize();
});
// IntersectionObserver to track viewport visibility
// Only attach ResizeObserver when table is visible, detach when off-screen
// This saves CPU for tables scrolled out of view
viewportObserver = new IntersectionObserver(
async (entries) => {
const entry = entries[0];
const wasIntersecting = isInViewport.value;
isInViewport.value = entry.isIntersecting;
if (entry.isIntersecting) {
// Table became visible - mini-table display:none is removed by class binding
// Wait for Vue to update DOM + browser to render before reconnecting
await nextTick();
await new Promise((resolve) => requestAnimationFrame(resolve));
if (miniTableRef.value && miniTableResizeObserver) {
miniTableResizeObserver.observe(miniTableRef.value);
}
// If transitioning from off-screen to visible, trigger immediate remeasure
// in case height changed while off-screen
if (!wasIntersecting && !lodash.isNil(lastMiniTableHeight) && lastMiniTableHeight > 0) {
handleResize();
}
} else {
// Table went off-screen - mini-table gets display:none via class binding
// Disconnect ResizeObserver to save CPU
if (miniTableResizeObserver) {
miniTableResizeObserver.disconnect();
}
}
},
{ threshold: 0 } // Trigger as soon as any part is visible
);
if (myTableRef.value) {
viewportObserver.observe(myTableRef.value);
}
// Initial observation of mini-table if already in viewport
if (miniTableRef.value && isInViewport.value && miniTableResizeObserver) {
miniTableResizeObserver.observe(miniTableRef.value);
}
});
// Cleanup on unmount
onUnmounted(() => {
if (miniTableResizeObserver) {
miniTableResizeObserver.disconnect();
miniTableResizeObserver = null;
}
if (viewportObserver) {
viewportObserver.disconnect();
viewportObserver = null;
}
});
const containerStyle = computed(() => {
const style: Record<string, string> = {};
if (prop.height !== undefined) {
style.height = typeof prop.height === "number" ? `${prop.height}px` : String(prop.height);
} else {
style.height = "100%";
}
if (prop.maxHeight !== undefined) {
style.maxHeight = typeof prop.maxHeight === "number" ? `${prop.maxHeight}px` : String(prop.maxHeight);
}
return style;
});
// Use explicit height if provided, otherwise use auto from auto-resizer
const resolvedHeight = (autoHeight: number) => {
if (prop.height !== undefined) {
return typeof prop.height === "number" ? prop.height : parseInt(String(prop.height));
}
return autoHeight;
};
const getValue = (value: any) => {
if ((typeof value === "undefined" || value === null || value === "") && value !== 0) {
return "--";
}
return value;
};
const formatStamp = (value: any) => {
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) => {
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";
};
// Helper to resolve timestamp column config to TzDateTime props
const resolveTimestampProps = (ts: TimestampValue): TzDateTimeConfig | null => {
if (!ts) return null;
if (ts === true) return { type: "unixMillis", displayFormat: prop.timestampFormat };
if (typeof ts === "string") {
// If it's 'unix', 'iso8601', or 'unixMillis' treat as type
if (["unix", "iso8601", "unixMillis"].includes(ts)) {
return { type: ts as TzDateTimeConfig["type"], displayFormat: prop.timestampFormat };
}
// Otherwise treat as valueFormat
return { valueFormat: ts, displayFormat: prop.timestampFormat };
}
// For object config, apply default displayFormat if not specified
const config = ts as TzDateTimeConfig;
if (!config.displayFormat) {
config.displayFormat = prop.timestampFormat;
}
return config;
};
const formatterByDist = (dictKey: string, cellData: any) => {
if (!dictKey) {
return getValue(cellData);
}
const mapping = state.dict[dictKey];
if (mapping == null) {
return getValue(cellData);
}
return mapping[cellData] == null ? cellData : mapping[cellData];
};
const formatCellValue = (cellData: T, column: ListTableColumn<T>, rowData: any) => {
if (column.dict) return formatterByDist(column.dict, cellData);
if (column.timestamp) return formatStamp(cellData);
if (column.filesize) return formatFileSize(cellData);
if (rowData.scheme) return formatterByDist(rowData.scheme + "_" + (column.dataKey || column.key), cellData);
return getValue(cellData);
};
const handleSizeChange = (val: number) => {
prop.example.size = val;
emit("query");
};
const handleCurrentChange = (val: number) => {
prop.example.page = val;
emit("query");
};
const onScroll = ({
scrollTop,
}: {
scrollTop: number;
scrollLeft?: number;
horizontal?: boolean;
vertical?: boolean;
}) => {
// Can be used for lazy loading or other scroll-based features
};
const onRowClick = (row: any) => {
emit("row-click", row);
};
const onCellClick = ({ row, column }: { row: any; column: any }) => {
emit("cell-click", row, column, null, null);
};
// Build columns for el-table-v2
const tableColumns = computed(() => {
return prop.columns.map((column): Column<T> => {
// Determine if column is fixed (left or right)
const isFixed = column.fixed === "left" || column.fixed === "right" || column.fixed === true;
// Resolve minWidth to a number
const resolvedMinWidth = match(column.minWidth)
.with(P.number, (n) => n)
.with(
P.string,
(str) => !isNaN(parseInt(str)),
(str) => parseInt(str)
)
.otherwise(() => 120);
// Resolve explicit width if provided
const explicitWidth = match(column.width)
.with(P.number, (w) => w)
.with(
P.string,
(str) => !isNaN(parseInt(str)),
(str) => parseInt(str)
)
.otherwise(() => undefined);
// Build column config
const col: Column<T> = {
key: column.key,
title: column.name || (column.i18n ? t(column.i18n) : column.key),
dataKey: column.dataKey || column.key,
align: column.align || "center",
fixed: match(column.fixed)
.with("left", () => FixedDir.LEFT)
.with("right", () => FixedDir.RIGHT)
.with(false, () => undefined)
.otherwise((value) => value),
minWidth: resolvedMinWidth,
};
// Width/flexGrow logic:
// - Fixed columns: use explicit width or minWidth, NO flexGrow (el-table-v2 fixed sub-tables don't handle flexGrow)
// - Non-fixed columns with explicit width: use that width
// - Non-fixed columns without width: use width:120 with flexGrow:1 for auto-distribution
if (isFixed) {
// Fixed columns get explicit width or minWidth, no flexGrow
col.width = explicitWidth !== undefined ? explicitWidth : resolvedMinWidth;
// No flexGrow for fixed columns
} else {
// Non-fixed columns
if (explicitWidth !== undefined) {
col.width = explicitWidth;
} else {
// Auto-distribute with flexGrow
col.width = 120;
col.flexGrow = 1;
}
}
// Cell renderer - uses renderCellContent which handles slot, cellRenderer, and built-in types
col.cellRenderer = (params: CellRendererParams<T>) => renderCellContent(params, false);
// Header cell renderer - use custom headerCellRenderer if provided
col.headerCellRenderer = (params: HeaderCellRendererParams<T>) => renderHeaderCellContent(params, false);
col._listTableColumn = column;
return col;
});
});
const MiniTableCell = (params: CellRendererParams<T>) => renderCellContent(params, true);
const MiniTableHeader = (params: HeaderCellRendererParams<T>) => renderHeaderCellContent(params, true);
// Shared cell renderer - used by both el-table-v2 and mini table for consistent rendering
const renderCellContent = (params: CellRendererParams<T>, isMiniTable = false) => {
const { cellData, rowData, column: elColumn } = params;
const column: ListTableColumn<T> = elColumn._listTableColumn;
const slotName = column.key;
// If custom cellRenderer is provided, use it (highest priority)
if (column.cellRenderer) {
return column.cellRenderer(params);
}
// If column has slot=true, render the parent's slot content
if (column.slot && slots[slotName]) {
return renderSlot(slots, slotName, { row: rowData });
}
// Handle timestamp display using TzDateTime component
if (column.timestamp && (typeof cellData === "string" || typeof cellData === "number")) {
const tzProps = resolveTimestampProps(column.timestamp);
if (tzProps) {
const { valueFormat, valueTz, displayFormat, locale, type } = tzProps;
return (
<TzDateTime
value={cellData}
valueFormat={valueFormat}
valueTz={valueTz}
displayFormat={displayFormat}
locale={locale}
type={type}
/>
);
}
}
// Handle dict display
if (column.dict) {
return (
<span class={isMiniTable ? "mini-cell-text" : "table-cell-text"}>{formatterByDist(column.dict, cellData)}</span>
);
}
// Handle formatting
const formatted = formatCellValue(cellData, column, rowData);
return <span class={isMiniTable ? "mini-cell-text" : "table-cell-text"}>{formatted}</span>;
};
const renderHeaderCellContent = (params: HeaderCellRendererParams<T>, isMiniTable = false) => {
const { column: elColumn } = params;
const column: ListTableColumn<T> = elColumn._listTableColumn;
if (column.headerCellRenderer) {
return column.headerCellRenderer(params);
}
const header = match(column)
.with({ name: P.select(P.string) }, (name) => name)
.with({ i18n: P.select(P.string) }, (i18n) => t(i18n))
.otherwise((c) => c.key);
return <span class={isMiniTable ? "mini-header-cell-text" : "table-header-cell-text"}>{header}</span>;
};
// Get mini cell style - mirrors the real table's column width/flex distribution
const getMiniCellStyle = ({ width, minWidth, maxWidth, flexGrow, flexShrink }: Column<T>): StyleValue => {
return { width: `${width}px`, minWidth: `${minWidth}px`, maxWidth: `${maxWidth}px`, flexGrow, flexShrink };
};
</script>
<style lang="scss" scoped>
.debug-info {
font-size: 9px;
}
.list-table-v2 {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
position: relative;
}
// Mini hidden table for measuring row height (flex boxes, not el-table-v2)
.mini-table {
position: absolute;
visibility: hidden;
pointer-events: none;
left: -9999px;
top: 0;
width: 100%;
font-size: var(--el-font-size-base);
}
// When off-screen, set display:none to remove from layout entirely (GPU optimization)
.mini-table.is-hidden {
display: none;
}
.mini-table-inner {
display: flex;
flex-direction: column;
width: 100%;
}
.mini-table-rows {
display: flex;
flex-direction: column;
flex: 1;
}
.mini-row {
display: flex;
align-items: stretch;
padding: v-bind("state.size.tablePad");
border-right: var(--el-table-border);
border-bottom: var(--el-table-border);
background: v-bind("state.style.tableBg");
color: v-bind("state.style.tableColor");
font-size: var(--el-font-size-base);
box-sizing: border-box;
overflow: hidden;
width: 100%;
min-height: min-content;
}
.mini-header {
background: v-bind("state.style.tableHeadBg");
font-weight: bold;
}
.mini-header-cell {
justify-content: center;
}
.mini-header-cell-text {
display: block;
}
.mini-cell {
display: flex;
flex-direction: row;
align-items: center;
padding: 4px;
box-sizing: border-box;
white-space: normal;
word-break: normal;
overflow: hidden;
}
.mini-cell-text {
display: block;
}
// CSS for real table cells (used by el-table-v2)
.table-cell-text {
display: block;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
box-sizing: border-box;
}
.table-header-cell-text {
display: block;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
box-sizing: border-box;
}
.my-table {
flex: 1;
width: 100%;
min-height: 0; // Important for flex child to shrink properly
display: flex;
flex-direction: column;
overflow: hidden;
position: relative; // For mini-table's containing block
}
.my-table :deep(.el-table-v2) {
--el-table-bg-color: v-bind("state.style.tableBg") !important;
--el-table-tr-bg-color: v-bind("state.style.tableBg") !important;
--el-table-expanded-cell-bg-color: v-bind("state.style.tableBg") !important;
--el-table-border-color: v-bind("state.style.tableBorderColor");
--el-table-text-color: v-bind("state.style.tableColor");
--el-table-header-text-color: v-bind("state.style.tableColor");
--el-table-row-hover-bg-color: v-bind("state.style.tableCurBg");
--el-table-current-row-bg-color: v-bind("state.style.tableCurBg");
--el-table-header-bg-color: v-bind("state.style.tableHeadBg");
--el-bg-color: v-bind("state.style.tableBg") !important;
// Border - el-table-v2 uses --el-table-border
--el-table-border: 1px solid var(--el-table-border-color);
}
.my-table :deep(.el-table-v2__row) {
background: v-bind("state.style.tableBg") !important;
}
.my-table :deep(.el-table-v2__row:hover) {
background: v-bind("state.style.tableCurBg") !important;
}
.my-table :deep(.el-table-v2__header-cell) {
padding: v-bind("state.size.tablePad");
border-right: var(--el-table-border);
color: v-bind("state.style.tableColor");
}
.my-table :deep(.el-table-v2__row-cell) {
padding: v-bind("state.size.tablePad");
border-right: var(--el-table-border);
color: v-bind("state.style.tableColor");
}
// Child row background for tree data
.my-table :deep(.el-table-v2__row[data-level="1"]),
.my-table :deep(.el-table-v2__row[data-level="3"]) {
background: v-bind("state.style.tableChildBg") !important;
}
.my-pagination {
display: flex;
justify-content: center;
padding-top: 5px;
flex-shrink: 0;
}
.my-pagination * {
--el-pagination-bg-color: v-bind("state.style.bodyBg") !important;
--el-pagination-disabled-bg-color: v-bind("state.style.bodyBg") !important;
--el-pagination-text-color: v-bind("state.style.color") !important;
--el-pagination-button-color: v-bind("state.style.color") !important;
--el-pagination-button-disabled-bg-color: v-bind("state.style.bodyBg") !important;
}
</style>
Loading…
Cancel
Save