Browse Source

feat: list-table-v2 PR2

dev
hechang27-sprt 2 months ago
parent
commit
1d1bf51629
  1. 3
      .trellis/workspace/hechang27-sprt/04-10-code-review.md
  2. 94
      examples/view/base/table-v2.vue
  3. 6
      packages/base/data/list-table-v2/index.ts
  4. 131
      packages/base/data/list-table-v2/list-table-v2.vue
  5. 116
      packages/base/data/list-table-v2/no-data.vue
  6. 6
      packages/base/data/list-table-v2/usePretextColumnWidths.ts
  7. 14
      packages/base/index.ts

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

@ -91,5 +91,4 @@ @@ -91,5 +91,4 @@
# 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...
› 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...

94
examples/view/base/table-v2.vue

@ -8,10 +8,25 @@ @@ -8,10 +8,25 @@
</nav>
<!-- ============================================================
SECTION 1: Basic Usage
SECTION 0: Layout
============================================================ -->
<section :id="sections[0].id" class="demo-section">
<h2 class="section-title">1. {{ sections[0].label }}</h2>
<h2 class="section-title">0. {{ sections[0].label }}</h2>
<p class="section-desc">Empty Table (Default min-height: 300px)</p>
<ListTableV2 :data="[]" :columns="emptyTableColumns" border />
<p class="section-desc">Empty Table (flex)</p>
<div style="display: flex; flex-direction: column; height: 200px; padding: 20px; border: solid 1px pink">
<ListTableV2 :data="[]" :columns="emptyTableColumns" border />
</div>
</section>
<!-- ============================================================
SECTION 1: Basic Usage
============================================================ -->
<section :id="sections[1].id" class="demo-section">
<h2 class="section-title">1. {{ sections[1].label }}</h2>
<p class="section-desc">Basic data binding with columns, pagination, and event handling.</p>
<div class="demo-toolbar">
@ -61,8 +76,8 @@ const basicColumns = [ @@ -61,8 +76,8 @@ const basicColumns = [
<!-- ============================================================
SECTION 2: Styling - Borders & Alignment
============================================================ -->
<section :id="sections[1].id" class="demo-section">
<h2 class="section-title">2. {{ sections[1].label }}</h2>
<section :id="sections[2].id" class="demo-section">
<h2 class="section-title">2. {{ sections[2].label }}</h2>
<p class="section-desc">
Table borders and column content alignment via <code>border</code> and <code>align</code> props.
</p>
@ -100,8 +115,8 @@ const styleColumns = [ @@ -100,8 +115,8 @@ const styleColumns = [
<!-- ============================================================
SECTION 3: Fixed Columns
============================================================ -->
<section :id="sections[2].id" class="demo-section">
<h2 class="section-title">3. {{ sections[2].label }}</h2>
<section :id="sections[3].id" class="demo-section">
<h2 class="section-title">3. {{ sections[3].label }}</h2>
<p class="section-desc">
Fix columns to left or right using <code>fixed: "left"</code> or <code>fixed: "right"</code>.
</p>
@ -141,8 +156,8 @@ const styleColumns = [ @@ -141,8 +156,8 @@ const styleColumns = [
<!-- ============================================================
SECTION 4: Data Formatting
============================================================ -->
<section :id="sections[3].id" class="demo-section">
<h2 class="section-title">4. {{ sections[3].label }}</h2>
<section :id="sections[4].id" class="demo-section">
<h2 class="section-title">4. {{ sections[4].label }}</h2>
<p class="section-desc">Built-in formatting for timestamps, dictionaries, and file sizes.</p>
<ListTableV2
@ -172,8 +187,8 @@ const styleColumns = [ @@ -172,8 +187,8 @@ const styleColumns = [
<!-- ============================================================
SECTION 5: Custom Cell Renderer
============================================================ -->
<section :id="sections[4].id" class="demo-section">
<h2 class="section-title">5. {{ sections[4].label }}</h2>
<section :id="sections[5].id" class="demo-section">
<h2 class="section-title">5. {{ sections[5].label }}</h2>
<p class="section-desc">
Custom cell content via <code>cellRenderer</code> function. Returns JSX. Critical: must work correctly with the
mini-table probe row for dynamic height measurement.
@ -222,8 +237,8 @@ const rendererColumns = [ @@ -222,8 +237,8 @@ const rendererColumns = [
<!-- ============================================================
SECTION 6: Custom Header Renderer
============================================================ -->
<section :id="sections[5].id" class="demo-section">
<h2 class="section-title">6. {{ sections[5].label }}</h2>
<section :id="sections[6].id" class="demo-section">
<h2 class="section-title">6. {{ sections[6].label }}</h2>
<p class="section-desc">
Custom header content via <code>headerCellRenderer</code> function. Must also sync with mini-table header probe.
</p>
@ -271,8 +286,8 @@ const headerColumns = [ @@ -271,8 +286,8 @@ const headerColumns = [
<!-- ============================================================
SECTION 7: Dynamic Height (Auto Probe)
============================================================ -->
<section :id="sections[6].id" class="demo-section">
<h2 class="section-title">7. {{ sections[6].label }}</h2>
<section :id="sections[7].id" class="demo-section">
<h2 class="section-title">7. {{ sections[7].label }}</h2>
<p class="section-desc">
Dynamic height mode uses a hidden mini-table to probe actual row and header heights.
<code>debug</code> shows estimated values. No <code>rowHeight</code> or <code>estimatedRowHeight</code> needed
@ -313,8 +328,8 @@ const headerColumns = [ @@ -313,8 +328,8 @@ const headerColumns = [
<!-- ============================================================
SECTION 8: Fixed Height Mode
============================================================ -->
<section :id="sections[7].id" class="demo-section">
<h2 class="section-title">8. {{ sections[7].label }}</h2>
<section :id="sections[8].id" class="demo-section">
<h2 class="section-title">8. {{ sections[8].label }}</h2>
<p class="section-desc">
Fixed height mode via <code>:row-height="60"</code>. Disables the mini-table probe. Better performance for large
datasets since no measurement needed.
@ -352,8 +367,8 @@ const headerColumns = [ @@ -352,8 +367,8 @@ const headerColumns = [
<!-- ============================================================
SECTION 9: Column Width Distribution
============================================================ -->
<section :id="sections[8].id" class="demo-section">
<h2 class="section-title">9. {{ sections[8].label }}</h2>
<section :id="sections[9].id" class="demo-section">
<h2 class="section-title">9. {{ sections[9].label }}</h2>
<p class="section-desc">
Control column widths with <code>width</code> (fixed) and <code>minWidth</code> (flexible). Columns without
explicit width use auto-derived flex factors greater than <code>1</code> to fill remaining space.
@ -383,8 +398,8 @@ const headerColumns = [ @@ -383,8 +398,8 @@ const headerColumns = [
<!-- ============================================================
SECTION 10: Width + Height + MaxHeight
============================================================ -->
<section :id="sections[9].id" class="demo-section">
<h2 class="section-title">10. {{ sections[9].label }}</h2>
<section :id="sections[10].id" class="demo-section">
<h2 class="section-title">10. {{ sections[10].label }}</h2>
<p class="section-desc">
Control table dimensions with <code>height</code>, <code>maxHeight</code>, and <code>headerHeight</code>.
Auto-resizer handles width automatically.
@ -422,8 +437,8 @@ const headerColumns = [ @@ -422,8 +437,8 @@ const headerColumns = [
<!-- ============================================================
SECTION 11: Slot-based Custom Cells
============================================================ -->
<section :id="sections[10].id" class="demo-section">
<h2 class="section-title">11. {{ sections[10].label }}</h2>
<section :id="sections[11].id" class="demo-section">
<h2 class="section-title">11. {{ sections[11].label }}</h2>
<p class="section-desc">
Custom cell content via Vue template slots. Use <code>slot: true</code> on column and define
<code>#columnKey</code> template in parent.
@ -450,7 +465,7 @@ const headerColumns = [ @@ -450,7 +465,7 @@ const headerColumns = [
</el-tag>
</template>
<template #actions="{ row }">
<div style="display: flex; flex: 1; justify-content: center; width: 100%; overflow: hidden; padding: 10% 20%">
<div style="display: flex; flex: 1; justify-content: center; width: 100%; overflow: hidden; padding: 10px">
<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>
@ -481,8 +496,8 @@ const slotColumns = [ @@ -481,8 +496,8 @@ const slotColumns = [
<!-- ============================================================
SECTION 12: i18n Support
============================================================ -->
<section :id="sections[11].id" class="demo-section">
<h2 class="section-title">12. {{ sections[11].label }}</h2>
<section :id="sections[12].id" class="demo-section">
<h2 class="section-title">12. {{ sections[12].label }}</h2>
<p class="section-desc">Column headers support i18n via <code>i18n</code> key (uses <code>vue3-i18n</code>).</p>
<ListTableV2
@ -509,8 +524,8 @@ const i18nColumns = [ @@ -509,8 +524,8 @@ const i18nColumns = [
<!-- ============================================================
SECTION 13: Combined Features Test
============================================================ -->
<section :id="sections[12].id" class="demo-section">
<h2 class="section-title">13. {{ sections[12].label }}</h2>
<section :id="sections[13].id" class="demo-section">
<h2 class="section-title">13. {{ sections[13].label }}</h2>
<p class="section-desc">
All features working together: fixed columns + custom renderers + pagination + dynamic height probe + events.
This is the stress test.
@ -563,8 +578,8 @@ const combinedColumns = [ @@ -563,8 +578,8 @@ const combinedColumns = [
<!-- ============================================================
SECTION 14: Custom Renderer + Fixed + Pagination
============================================================ -->
<section :id="sections[13].id" class="demo-section">
<h2 class="section-title">14. {{ sections[13].label }}</h2>
<section :id="sections[14].id" class="demo-section">
<h2 class="section-title">14. {{ sections[14].label }}</h2>
<p class="section-desc">
Edge case: custom <code>cellRenderer</code> combined with <code>fixed</code> columns and
<code>pagination</code>. Verifies mini-table probe handles custom renderers correctly across page changes.
@ -605,7 +620,7 @@ const edgeColumns = [ @@ -605,7 +620,7 @@ const edgeColumns = [
<script lang="tsx" setup>
import { useStore } from "vuex";
import { reactive, ref, computed, onMounted } from "vue";
import { ListTableV2, SearchRow, NoobInput, NoobSelect, Infomation } from "noob-mengyxu";
import { ListTableV2, SearchRow, NoobInput, NoobSelect, Infomation, useSysDict } from "noob-mengyxu";
import { useI18n } from "vue3-i18n";
import { LoremIpsum } from "lorem-ipsum";
import { ElTag, ElButton, ElLink } from "element-plus";
@ -617,6 +632,8 @@ import { @@ -617,6 +632,8 @@ import {
} from "element-plus/es/components/table-v2/src/types.mjs";
import { type ListTableColumn } from "../../../packages/base/data/list-table-v2";
const { updateDict } = useSysDict();
// --- Data Generator ---
const generateRows = (count: number) => {
const rows: Array<Record<string, any>> = [];
@ -648,6 +665,7 @@ const { t } = useI18n(); @@ -648,6 +665,7 @@ const { t } = useI18n();
// --- Section Navigation ---
const sections = [
{ id: "s0-empty", label: "Layout" },
{ id: "s1-basic", label: "Basic Usage" },
{ id: "s2-styling", label: "Styling" },
{ id: "s3-fixed", label: "Fixed Columns" },
@ -677,6 +695,17 @@ const testResults = reactive<Record<string, any>>({ @@ -677,6 +695,17 @@ const testResults = reactive<Record<string, any>>({
combined: {},
});
// ================================================================
// SECTION 0: Empty Table
// ================================================================
const emptyTableColumns = [
{ key: "id", name: "ID" },
{ key: "caseName", name: "Case Name" },
{ key: "taskName", name: "Task Name" },
{ key: "userId", name: "User ID", dict: "test" },
{ key: "createTime", name: "Create Time", timestamp: "unix" },
];
// ================================================================
// SECTION 1: Basic
// ================================================================
@ -686,7 +715,7 @@ const basicColumns = [ @@ -686,7 +715,7 @@ const basicColumns = [
{ key: "id", name: "ID" },
{ key: "caseName", name: "Case Name" },
{ key: "taskName", name: "Task Name" },
{ key: "userId", name: "User ID" },
{ key: "userId", name: "User ID", dict: "test" },
{ key: "createTime", name: "Create Time", timestamp: "unix" },
];
const handleBasicQuery = () => {
@ -1025,6 +1054,9 @@ const toggleRendererType = () => { @@ -1025,6 +1054,9 @@ const toggleRendererType = () => {
onMounted(() => {
console.log("Table V2 Demo mounted");
updateDict(["test"], {
test: () => Object.fromEntries(Array.from({ length: 10 }, (_, i) => [`user${i}`, `用户${i}`])),
});
});
</script>

6
packages/base/data/list-table-v2/index.ts

@ -4,6 +4,12 @@ @@ -4,6 +4,12 @@
* Hooks and utilities for the list-table-v2 component.
*/
import ListTableV2 from "./list-table-v2.vue";
import NoData from "./no-data.vue";
export { ListTableV2, NoData };
export default ListTableV2;
// Types
export type {
ListTableColumn,

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<template>
<div class="list-table-v2">
<div class="list-table-v2" :style="tableOuterContainerStyle">
<!-- Debug info -->
<div v-if="debug" class="debug-info">
<json-view
@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
:virtual="true"
:show-icon="true"
:show-double-quotes="false"
:height="200"
inline
collapse-fully-inline-node
/>
@ -18,16 +19,18 @@ @@ -18,16 +19,18 @@
<!-- Header row (fixed) -->
<div class="table-header" :style="{ height: `${resolvedHeaderHeight}px` }">
<div v-for="col in visibleColumns" :key="col.key" class="header-cell" :style="getColumnStyle(col)">
<div class="header-cell-content">
<slot :name="`header-${col.key}`">
<span class="header-cell-text" :style="getTextStyle(col)">{{ getHeaderText(col) }}</span>
</slot>
</div>
</div>
</div>
<!-- Scrollable body -->
<div ref="scrollBodyRef" class="table-body" :style="{ height: `${viewportHeight}px` }" @scroll="handleScroll">
<!-- Total height spacer -->
<div class="table-spacer" :style="{ height: `${virtualTotalHeight}px` }">
<div v-if="pageData.length" class="table-spacer" :style="{ height: `${virtualTotalHeight}px` }">
<!-- Visible rows -->
<div
v-for="virt in visibleRows"
@ -46,6 +49,7 @@ @@ -46,6 +49,7 @@
:style="getColumnStyle(col)"
@click="handleCellClick(virt.index, col)"
>
<div class="table-cell-content">
<slot :name="col.key" :row="getRow(virt.index)">
<span class="cell-text" :style="getTextStyle(col)">{{
getRow(virt.index) ? formatCellValue(getRow(virt.index)!, col) : "--"
@ -55,6 +59,11 @@ @@ -55,6 +59,11 @@
</div>
</div>
</div>
<div v-else class="table-spacer-empty">
<NoData />
</div>
</div>
</div>
<!-- Pagination -->
@ -67,7 +76,7 @@ @@ -67,7 +76,7 @@
:page-sizes="[10, 20, 50, 100, 200]"
:page-size="example.size"
layout="total, sizes, prev, pager, next, jumper"
:total="paginationTotal"
:total="totalRows"
/>
</div>
</div>
@ -83,8 +92,9 @@ import { usePretextColumnWidths } from "./usePretextColumnWidths"; @@ -83,8 +92,9 @@ import { usePretextColumnWidths } from "./usePretextColumnWidths";
import { resolveRowHeights } from "./usePretextRowHeights";
import { useVirtualRows } from "./useVirtualRows";
import { formatTimestampFromValue } from "../../../../plugs/composables";
import { match } from "ts-pattern";
import { match, P } from "ts-pattern";
import { JsonView } from "noob-mengyxu";
import NoData from "./no-data.vue";
import { ListTableProps } from "./types";
const DEFAULT_FONT = "14px Microsoft YaHei, sans-serif";
@ -129,6 +139,27 @@ const props = withDefaults(defineProps<Props>(), { @@ -129,6 +139,27 @@ const props = withDefaults(defineProps<Props>(), {
debug: false,
});
const sizeRegex = /(\d+(?:\.\d+)?(?:px|em|rem|pt|%|vh|vw))/i;
const makeBold = (font: string) => {
// 1. Check if 'bold' or '700' already exists to avoid duplicates
if (/\bbold\b|\b[7-9]00\b/.test(font)) {
return font;
}
// 2. Regex to find the font size (the first number followed by a CSS unit)
// This looks for the mandatory size component
if (sizeRegex.test(font)) {
// Insert 'bold ' immediately before the size
return font.replace(sizeRegex, "bold $1");
}
// Fallback: If the string is malformed, appending to front is the safest bet
return `bold ${font}`;
};
const headerFont = computed(() => props.headerFont ?? makeBold(props.font) ?? DEFAULT_HEADER_FONT);
const emit = defineEmits<{
(e: "query"): void;
(e: "selection-change", selection: any[]): void;
@ -147,8 +178,8 @@ const pageData = computed<T[]>(() => { @@ -147,8 +178,8 @@ const pageData = computed<T[]>(() => {
return props.data ?? [];
});
const paginationTotal = computed(() => {
if (!props.page) return 0;
const totalRows = computed(() => {
if (!props.page) return props.data?.length ?? 0;
return props.data.total ?? 0;
});
@ -210,7 +241,7 @@ const formatFileSize = (value: any): string => { @@ -210,7 +241,7 @@ const formatFileSize = (value: any): string => {
const formatByDict = (dictKey: string, cellData: any): string => {
if (!dictKey) return `UNKNOWN_DICT: ${dictKey}`;
const mapping = (state as any).dict[dictKey];
const mapping = state.dict[dictKey];
if (mapping == null) return `UNKNOWN_DICT: ${dictKey}`;
return display(mapping[cellData]);
};
@ -225,7 +256,8 @@ const { computedConfigs, totalFlexBasis, columnWidths } = usePretextColumnWidths @@ -225,7 +256,8 @@ const { computedConfigs, totalFlexBasis, columnWidths } = usePretextColumnWidths
columnsRef,
containerWidth,
formatCellValue,
{ font: props.font, headerFont: DEFAULT_HEADER_FONT /* TODO: add props.headerFont */ }
t,
{ font: props.font, headerFont: headerFont.value }
);
// Row heights via pretext - use actual column widths from flexbox algorithm
@ -267,6 +299,24 @@ const visibleColumns = computed(() => { @@ -267,6 +299,24 @@ const visibleColumns = computed(() => {
// =============================================================================
// Computed styles
// =============================================================================
const parseCssSize = (size: number | string | undefined, nonZero: boolean = true) => {
const num = typeof size === "string" ? parseFloat(size) : size;
return match(size)
.with(P.string.regex(sizeRegex), (str) => str)
.with(
P.string,
(_) => num != null && isFinite(num) && (nonZero ? num > 0 : num >= 0),
(_) => `${num}px`
)
.with(
P.number,
(num) => isFinite(num) && (nonZero ? num > 0 : num >= 0),
(num) => num
)
.otherwise(() => undefined);
};
const tableContainerStyle = computed(() => {
const style: CSSProperties = {
display: "flex",
@ -274,15 +324,23 @@ const tableContainerStyle = computed(() => { @@ -274,15 +324,23 @@ const tableContainerStyle = computed(() => {
width: "100%",
overflow: "hidden" as const,
};
if (props.height !== undefined) {
const h = String(props.height);
style.maxHeight = parseCssSize(props.maxHeight);
if ((style.height = parseCssSize(props.height))) {
style.flex = "unset";
style.height = h.endsWith("px") ? h : `${h}px`;
} else {
style.minHeight = parseCssSize(props.minHeight);
}
if (props.maxHeight !== undefined) {
const mh = String(props.maxHeight);
style.maxHeight = mh.endsWith("px") ? mh : `${mh}px`;
return style;
});
const tableOuterContainerStyle = computed(() => {
const style: CSSProperties = {};
if (props.height !== undefined) {
style.flex = "unset";
}
return style;
});
@ -397,7 +455,7 @@ const debugInfo = computed(() => ({ @@ -397,7 +455,7 @@ const debugInfo = computed(() => ({
cellHeights: cellHeights.value?.map((rowCellHeights) =>
rowCellHeights.map((entry) => (entry ? `${entry.isCustomRenderer ? "*" : ""}${entry.height}` : "null"))
),
totalRows: pageData.value.length,
totalRows,
columnWidths: columnWidths.value.map((w) => Math.round(w)),
columnConfigs: computedConfigs,
}));
@ -450,7 +508,7 @@ onUnmounted(() => { @@ -450,7 +508,7 @@ onUnmounted(() => {
.list-table-v2 {
display: flex;
flex-direction: column;
flex: 0 0 auto;
flex: 1;
overflow: hidden;
position: relative;
}
@ -488,6 +546,19 @@ onUnmounted(() => { @@ -488,6 +546,19 @@ onUnmounted(() => {
}
}
.header-cell-content {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
max-width: 100%;
overflow: hidden;
box-sizing: border-box;
}
.header-cell-text {
display: block;
width: 100%;
@ -495,8 +566,7 @@ onUnmounted(() => { @@ -495,8 +566,7 @@ onUnmounted(() => {
text-overflow: ellipsis;
white-space: nowrap;
color: v-bind("state.style.tableColor");
font: v-bind("props.headerFont ?? props.font ?? DEFAULT_HEADER_FONT");
font-weight: v-bind("!props.headerFont && props.font ? 'bold' : 'unset'");
font: v-bind("headerFont");
}
.table-body {
@ -512,6 +582,12 @@ onUnmounted(() => { @@ -512,6 +582,12 @@ onUnmounted(() => {
width: 100%;
}
.table-spacer-empty {
position: relative;
width: 100%;
height: 100%;
}
.table-row {
position: absolute;
left: 0;
@ -546,6 +622,25 @@ onUnmounted(() => { @@ -546,6 +622,25 @@ onUnmounted(() => {
}
}
.table-cell-content {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
max-width: 100%;
overflow: hidden;
box-sizing: border-box;
}
.header-cell-content > *,
.table-cell-content > * {
min-width: 0;
max-width: 100%;
}
.cell-text {
display: block;
width: 100%;

116
packages/base/data/list-table-v2/no-data.vue

@ -0,0 +1,116 @@ @@ -0,0 +1,116 @@
<template>
<div class="list-table-empty">
<div class="list-table-empty__image">
<svg
viewBox="0 0 79 86"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs>
<linearGradient :id="`linearGradient-1-${svgIdSuffix}`" x1="38.8503086%" y1="0%" x2="61.1496914%" y2="100%">
<stop stop-color="#fcfcfd" offset="0%"></stop>
<stop stop-color="#eeeff3" offset="100%"></stop>
</linearGradient>
<linearGradient :id="`linearGradient-2-${svgIdSuffix}`" x1="0%" y1="9.5%" x2="100%" y2="90.5%">
<stop stop-color="#fcfcfd" offset="0%"></stop>
<stop stop-color="#e9ebef" offset="100%"></stop>
</linearGradient>
<rect :id="`path-3-${svgIdSuffix}`" x="0" y="0" width="17" height="36"></rect>
</defs>
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-1268.000000, -535.000000)">
<g transform="translate(1268.000000, 535.000000)">
<path
d="M39.5,86 C61.3152476,86 79,83.9106622 79,81.3333333 C79,78.7560045 57.3152476,78 35.5,78 C13.6847524,78 0,78.7560045 0,81.3333333 C0,83.9106622 17.6847524,86 39.5,86 Z"
fill="#f7f8fc"
></path>
<polygon
fill="#e5e7e9"
transform="translate(27.500000, 51.500000) scale(1, -1) translate(-27.500000, -51.500000) "
points="13 58 53 58 42 45 2 45"
></polygon>
<g
transform="translate(34.500000, 31.500000) scale(-1, 1) rotate(-25.000000) translate(-34.500000, -31.500000) translate(7.000000, 10.000000)"
>
<polygon
fill="#e5e7e9"
transform="translate(11.500000, 5.000000) scale(1, -1) translate(-11.500000, -5.000000) "
points="2.84078316e-14 3 18 3 23 7 5 7"
></polygon>
<polygon fill="#edeef2" points="-3.69149156e-15 7 38 7 38 43 -3.69149156e-15 43"></polygon>
<rect
:fill="`url(#linearGradient-1-${svgIdSuffix})`"
transform="translate(46.500000, 25.000000) scale(-1, 1) translate(-46.500000, -25.000000) "
x="38"
y="7"
width="17"
height="36"
></rect>
<polygon
fill="#f8f9fb"
transform="translate(39.500000, 3.500000) scale(-1, 1) translate(-39.500000, -3.500000) "
points="24 7 41 7 55 -3.63806207e-12 38 -3.63806207e-12"
></polygon>
</g>
<rect :fill="`url(#linearGradient-2-${svgIdSuffix})`" x="13" y="45" width="40" height="36"></rect>
<g transform="translate(53.000000, 45.000000)">
<use
fill="#e0e3e9"
transform="translate(8.500000, 18.000000) scale(-1, 1) translate(-8.500000, -18.000000) "
:xlink:href="`#path-3-${svgIdSuffix}`"
></use>
</g>
<polygon
fill="#f8f9fb"
transform="translate(66.000000, 51.500000) scale(-1, 1) translate(-66.000000, -51.500000) "
points="62 45 79 45 70 58 53 58"
></polygon>
</g>
</g>
</g>
</svg>
</div>
<div class="list-table-empty__description"><p>No Data</p></div>
</div>
</template>
<script lang="ts" setup>
import { getCurrentInstance } from "vue";
const svgIdSuffix = `no-data-${getCurrentInstance()?.uid ?? "static"}`;
</script>
<style lang="scss" scoped>
.list-table-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 40px 0;
text-align: center;
}
.list-table-empty__image {
width: 160px;
}
.list-table-empty__image svg {
display: block;
width: 100%;
height: auto;
}
.list-table-empty__description {
margin-top: 20px;
}
.list-table-empty__description p {
margin: 0;
color: #909399;
font-size: 14px;
line-height: 1.4;
}
</style>

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

@ -12,9 +12,6 @@ @@ -12,9 +12,6 @@
import { computed, type Ref } from "vue";
import { measureShrinkWrapWidth } from "./measureText";
import type { ListTableColumn } from "./types";
const DEFAULT_FONT = "14px Inter, sans-serif";
const DEFAULT_HEADER_FONT = "bold 14px Inter, sans-serif";
const DEFAULT_PADDING = 16;
const CELL_PADDING = DEFAULT_PADDING * 2; // left + right
const MAX_WIDTH = Number.POSITIVE_INFINITY;
@ -189,6 +186,7 @@ export function usePretextColumnWidths<T>( @@ -189,6 +186,7 @@ export function usePretextColumnWidths<T>(
columns: Ref<ListTableColumn<T>[]>,
containerWidth: Ref<number>,
formatCellValue: (row: T, col: ListTableColumn<T>) => string,
i18n: (key: string) => string,
style: {
font: string;
headerFont: string;
@ -218,7 +216,7 @@ export function usePretextColumnWidths<T>( @@ -218,7 +216,7 @@ export function usePretextColumnWidths<T>(
col.maxWidth !== undefined && Number.isFinite(Number(col.maxWidth)) ? Number(col.maxWidth) : undefined;
// Measure header width
const headerText = col.name || col.i18n || colKey;
const headerText = col.name || (col.i18n ? i18n(col.i18n) : "") || colKey;
const headerWidth = measureShrinkWrapWidth(headerText, headerFont) + CELL_PADDING;
// Measure sampled cell widths

14
packages/base/index.ts

@ -12,6 +12,7 @@ import WsMonitorToggle from "./item/ws-monitor-toggle.vue"; @@ -12,6 +12,7 @@ import WsMonitorToggle from "./item/ws-monitor-toggle.vue";
import SearchRow from "./data/search-row.vue";
import ListTable from "./data/list-table.vue";
import ListTableV2 from "./data/list-table-v2/list-table-v2.vue";
import NoData from "./data/list-table-v2/no-data.vue";
import JsonView from "./data/json-view/json-view.vue";
import Infomation from "./data/infomation.vue";
import ModifyForm from "./data/modify-form.vue";
@ -33,4 +34,15 @@ export { @@ -33,4 +34,15 @@ export {
WsMonitorToggle,
};
export { SearchRow, ListTable, ListTableV2, JsonView, ListTableDialog, Infomation, ModifyForm, Descriptions, TableAction };
export {
SearchRow,
ListTable,
ListTableV2,
NoData,
JsonView,
ListTableDialog,
Infomation,
ModifyForm,
Descriptions,
TableAction,
};

Loading…
Cancel
Save