Browse Source

WIP: list-table-v2

dev
hechang27-sprt 2 months ago
parent
commit
e97043c534
  1. 2
      AGENTS.md
  2. 2
      CLAUDE.md
  3. 2
      examples/view/base/table-v2.vue
  4. 94
      packages/base/data/list-table-v2/list-table-v2.vue
  5. 14
      packages/base/data/list-table-v2/usePretextColumnWidths.ts
  6. 24
      packages/base/data/list-table-v2/usePretextRowHeights.ts

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** (721 symbols, 1559 relationships, 53 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **noob-components** (794 symbols, 1774 relationships, 59 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** (721 symbols, 1559 relationships, 53 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **noob-components** (794 symbols, 1774 relationships, 59 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
examples/view/base/table-v2.vue

@ -450,8 +450,10 @@ const headerColumns = [ @@ -450,8 +450,10 @@ const headerColumns = [
</el-tag>
</template>
<template #actions="{ row }">
<div style="display: flex; flex: 1; justify-content: center; width: 100%; overflow: hidden; padding: 10% 20%">
<el-button link type="primary" size="small" @click="onSlotAction(row)">Edit</el-button>
<el-button link type="danger" size="small" @click="onSlotAction(row)">Delete</el-button>
</div>
</template>
</ListTableV2>

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

@ -2,7 +2,15 @@ @@ -2,7 +2,15 @@
<div class="list-table-v2">
<!-- Debug info -->
<div v-if="debug" class="debug-info">
<json-view :data="debugInfo" :deep="3" :virtual="true" :show-icon="true" :show-double-quotes="false" />
<json-view
:data="debugInfo"
:deep="1"
:virtual="true"
:show-icon="true"
:show-double-quotes="false"
inline
collapse-fully-inline-node
/>
</div>
<!-- Main table container with ResizeObserver for width tracking -->
@ -22,25 +30,25 @@ @@ -22,25 +30,25 @@
<div class="table-spacer" :style="{ height: `${virtualTotalHeight}px` }">
<!-- Visible rows -->
<div
v-for="row in visibleRows"
:key="row.index"
v-for="virt in visibleRows"
:key="lodash.get(getRow(virt.index), rowKey) ?? virt.index"
class="table-row"
:style="{
transform: `translateY(${row.offsetY}px)`,
height: `${row.height}px`,
transform: `translateY(${virt.offsetY}px)`,
height: `${virt.height}px`,
}"
@click="handleRowClick(row.index)"
@click="handleRowClick(virt.index)"
>
<div
v-for="col in visibleColumns"
:key="col.key"
class="table-cell"
:style="getColumnStyle(col)"
@click="handleCellClick(row.index, col)"
@click="handleCellClick(virt.index, col)"
>
<slot :name="col.key" :row="getRow(row.index)">
<slot :name="col.key" :row="getRow(virt.index)">
<span class="cell-text" :style="getTextStyle(col)">{{
getRow(row.index) ? formatCellValue(getRow(row.index)!, col) : "--"
getRow(virt.index) ? formatCellValue(getRow(virt.index)!, col) : "--"
}}</span>
</slot>
</div>
@ -66,7 +74,7 @@ @@ -66,7 +74,7 @@
</template>
<script lang="tsx" setup generic="T">
import { ref, computed, onMounted, onUnmounted, toRef, StyleValue, CSSProperties } from "vue";
import { ref, computed, onMounted, onUnmounted, toRef, CSSProperties, watch } from "vue";
import { useStore } from "vuex";
import { useI18n } from "vue3-i18n";
import * as lodash from "lodash-es";
@ -78,8 +86,12 @@ import { formatTimestampFromValue } from "../../../../plugs/composables"; @@ -78,8 +86,12 @@ import { formatTimestampFromValue } from "../../../../plugs/composables";
import { match } from "ts-pattern";
import { JsonView } from "noob-mengyxu";
const DEFAULT_FONT = "14px sans-serif";
const DEFAULT_FONT = "14px Microsoft YaHei, sans-serif";
const DEFAULT_HEADER_FONT = "bold 16px Microsoft YaHei, sans-serif";
const DEFAULT_TEXT_MAX_WIDTH = 400;
const DEFAULT_LINE_HEIGHT = 20;
const DEFAULT_TOP_BOTTOM_PADDING = 8;
const DEFAULT_LEFT_RIGHT_PADDING = 4;
const { t } = useI18n();
const { state } = useStore();
@ -113,6 +125,7 @@ interface Props { @@ -113,6 +125,7 @@ interface Props {
estimatedRowHeight?: number;
headerHeight?: number;
font?: string;
headerFont?: string;
debug?: boolean;
}
@ -170,6 +183,8 @@ const pageDataRef = toRef(pageData); @@ -170,6 +183,8 @@ const pageDataRef = toRef(pageData);
// =============================================================================
// Cell formatting
// =============================================================================
const isNullish = (value: any) => value === undefined || value === null || value === "";
const getHeaderText = (col: ListTableColumn<T>): string => {
if (col.name) return col.name;
if (col.i18n) return t(col.i18n);
@ -180,7 +195,7 @@ const formatCellValue = (row: T, col: ListTableColumn<T>): string => { @@ -180,7 +195,7 @@ 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);
return formatByDict(col.dict, value);
}
if (col.timestamp) {
if ((typeof value !== "string" && typeof value !== "number") || value === "") {
@ -198,12 +213,7 @@ const formatCellValue = (row: T, col: ListTableColumn<T>): string => { @@ -198,12 +213,7 @@ const formatCellValue = (row: T, col: ListTableColumn<T>): string => {
return String(value);
};
const getValue = (value: any): string => {
if ((typeof value === "undefined" || value === null || value === "") && value !== 0) {
return "--";
}
return value;
};
const display = (value: any): string => (isNullish(value) ? "--" : String(value));
const formatFileSize = (value: any): string => {
const k = value / 1024;
@ -215,11 +225,12 @@ const formatFileSize = (value: any): string => { @@ -215,11 +225,12 @@ const formatFileSize = (value: any): string => {
return g.toFixed(2) + "G";
};
const formatterByDist = (dictKey: string, cellData: any): string => {
if (!dictKey) return getValue(cellData);
const formatByDict = (dictKey: string, cellData: any): string => {
if (!dictKey) return `UNKNOWN_DICT: ${dictKey}`;
const mapping = (state as any).dict[dictKey];
if (mapping == null) return getValue(cellData);
return mapping[cellData] == null ? cellData : mapping[cellData];
if (mapping == null) return `UNKNOWN_DICT: ${dictKey}`;
return display(mapping[cellData]);
};
// =============================================================================
@ -231,11 +242,8 @@ const { computedConfigs, totalFlexBasis, columnWidths } = usePretextColumnWidths @@ -231,11 +242,8 @@ const { computedConfigs, totalFlexBasis, columnWidths } = usePretextColumnWidths
pageDataRef,
columnsRef,
containerWidth,
{
font: "14px Inter, sans-serif",
headerFont: "bold 14px Inter, sans-serif",
formatCellValue,
}
{ font: props.font, headerFont: DEFAULT_HEADER_FONT /* TODO: add props.headerFont */ }
);
// Row heights via pretext - use actual column widths from flexbox algorithm
@ -247,7 +255,15 @@ const { rowHeights, cellHeights } = resolveRowHeights( @@ -247,7 +255,15 @@ const { rowHeights, cellHeights } = resolveRowHeights(
formatCellValue,
{
font: props.font,
lineHeight: 20,
lineHeight: DEFAULT_LINE_HEIGHT,
padding: {
top: DEFAULT_TOP_BOTTOM_PADDING,
bottom: DEFAULT_TOP_BOTTOM_PADDING,
left: DEFAULT_LEFT_RIGHT_PADDING + 1,
right: DEFAULT_LEFT_RIGHT_PADDING,
},
},
{
debug: props.debug,
fixedRowHeight: props.rowHeight,
}
@ -278,12 +294,13 @@ const tableContainerStyle = computed(() => { @@ -278,12 +294,13 @@ const tableContainerStyle = computed(() => {
};
if (props.height !== undefined) {
const h = String(props.height);
style.maxHeight = h.endsWith("px") ? h : `${h}px`;
style.flex = "unset";
style.height = h.endsWith("px") ? h : `${h}px`;
}
if (props.maxHeight !== undefined) {
const mh = String(props.maxHeight);
style.maxHeight = mh.endsWith("px") ? mh : `${mh}px`;
}
// if (props.maxHeight !== undefined) {
// const mh = String(props.maxHeight);
// style.maxHeight = mh.endsWith("px") ? mh : `${mh}px`;
// }
return style;
});
@ -394,12 +411,12 @@ const handleCurrentChange = (val: number) => { @@ -394,12 +411,12 @@ const handleCurrentChange = (val: number) => {
const debugInfo = computed(() => ({
containerWidth,
virtualTotalHeight,
visibleRows: visibleRows.value.map((entry) => entry.height).join(","),
visibleRows: visibleRows.value.map((entry) => entry.height),
cellHeights: cellHeights.value?.map((rowCellHeights) =>
rowCellHeights.map((entry) => (entry ? `${entry.isCustomRenderer ? "*" : ""}${entry.height}` : "null")).join(",")
rowCellHeights.map((entry) => (entry ? `${entry.isCustomRenderer ? "*" : ""}${entry.height}` : "null"))
),
totalRows: pageData.value.length,
columnWidths: columnWidths.value.map((w) => Math.round(w)).join(","),
columnWidths: columnWidths.value.map((w) => Math.round(w)),
columnConfigs: computedConfigs,
}));
@ -477,7 +494,7 @@ onUnmounted(() => { @@ -477,7 +494,7 @@ onUnmounted(() => {
flex: 1 1 120px;
align-items: center;
justify-content: center;
padding: 8px 4px;
padding: v-bind("`${DEFAULT_TOP_BOTTOM_PADDING}px ${DEFAULT_LEFT_RIGHT_PADDING}px`");
box-sizing: border-box;
border-right: 1px solid v-bind("state.style.tableBorderColor");
overflow: hidden;
@ -496,8 +513,8 @@ onUnmounted(() => { @@ -496,8 +513,8 @@ onUnmounted(() => {
text-overflow: ellipsis;
white-space: nowrap;
color: v-bind("state.style.tableColor");
font-weight: bold;
font-size: var(--el-font-size-base);
font: v-bind("props.headerFont ?? props.font ?? DEFAULT_HEADER_FONT");
font-weight: v-bind("!props.headerFont && props.font ? 'bold' : 'unset'");
}
.table-body {
@ -556,6 +573,7 @@ onUnmounted(() => { @@ -556,6 +573,7 @@ onUnmounted(() => {
word-break: break-word;
color: v-bind("state.style.tableColor");
font: v-bind("props.font");
line-height: v-bind("`${DEFAULT_LINE_HEIGHT}px`");
}
.my-pagination {

14
packages/base/data/list-table-v2/usePretextColumnWidths.ts

@ -188,17 +188,17 @@ export function usePretextColumnWidths<T>( @@ -188,17 +188,17 @@ export function usePretextColumnWidths<T>(
data: Ref<T[]>,
columns: Ref<ListTableColumn<T>[]>,
containerWidth: Ref<number>,
formatCellValue: (row: T, col: ListTableColumn<T>) => string,
style: {
font: string;
headerFont: string;
},
options?: {
font?: string;
headerFont?: string;
sampleSize?: number;
formatCellValue?: (row: T, col: ListTableColumn<T>) => string;
}
) {
const font = options?.font ?? DEFAULT_FONT;
const headerFont = options?.headerFont ?? DEFAULT_HEADER_FONT;
const { font, headerFont } = style;
const sampleSize = options?.sampleSize ?? 100;
const formatCellValue = options?.formatCellValue;
const computedConfigs = computed<ColumnFlexConfig[]>(() => {
if (!columns.value.length) return [];
@ -225,7 +225,7 @@ export function usePretextColumnWidths<T>( @@ -225,7 +225,7 @@ export function usePretextColumnWidths<T>(
const cellWidths: number[] = [];
for (const row of sampled) {
const rawValue = (row as any)[col.dataKey || colKey];
const cellText = formatCellValue?.(row, col) ?? (rawValue == null ? "" : String(rawValue));
const cellText = formatCellValue(row, col) ?? (rawValue == null ? "" : String(rawValue));
if (cellText) {
const w = measureShrinkWrapWidth(cellText, font) + CELL_PADDING;
cellWidths.push(w);

24
packages/base/data/list-table-v2/usePretextRowHeights.ts

@ -10,11 +10,6 @@ import { measureTextHeight } from "./measureText"; @@ -10,11 +10,6 @@ import { measureTextHeight } from "./measureText";
import type { ListTableColumn } from "./types";
import { toRefs } from "@vueuse/core";
const DEFAULT_FONT = "14px Inter, sans-serif";
const DEFAULT_LINE_HEIGHT = 20;
const DEFAULT_ROW_PADDING = 5;
const CELL_VERTICAL_PADDING = 12; // top + bottom per cell
export interface RowHeightEntry {
height: number;
isCustomRenderer: boolean;
@ -26,17 +21,17 @@ export function resolveRowHeights<T>( @@ -26,17 +21,17 @@ export function resolveRowHeights<T>(
columnWidths: Ref<number[]>,
lineMaxWidth: number,
formatCellValue: (row: T, col: ListTableColumn<T>) => string,
style: {
font: string;
lineHeight: number;
padding: { top: number; bottom: number; left: number; right: number };
},
options?: {
font?: string;
lineHeight?: number;
rowPadding?: number;
fixedRowHeight?: number;
debug?: boolean;
}
) {
const font = options?.font ?? DEFAULT_FONT;
const lineHeight = options?.lineHeight ?? DEFAULT_LINE_HEIGHT;
const rowPadding = options?.rowPadding ?? DEFAULT_ROW_PADDING;
const { font, lineHeight, padding } = style;
const { rowHeights, cellHeights } = toRefs(
computed(() => {
@ -77,7 +72,10 @@ export function resolveRowHeights<T>( @@ -77,7 +72,10 @@ export function resolveRowHeights<T>(
if (!cellText) throw "Invalid Cell Text";
// Calculate available width for text (excluding cell padding)
const availableWidth = Math.min(colWidth - CELL_VERTICAL_PADDING * 2, col.maxTextWidth ?? lineMaxWidth);
const availableWidth = Math.min(
colWidth - padding.left - padding.right,
col.maxTextWidth ?? lineMaxWidth
);
if (availableWidth <= 0) throw "Invalid Column Width";
rowHeightEntry = {
@ -99,7 +97,7 @@ export function resolveRowHeights<T>( @@ -99,7 +97,7 @@ export function resolveRowHeights<T>(
}
}
const rowHeight = maxCellHeight + rowPadding * 2;
const rowHeight = maxCellHeight + padding.top + padding.bottom;
rowHeights.push(rowHeight);
cellHeights?.push(rowCellHeights);
}

Loading…
Cancel
Save