|
|
|
|
<template>
|
|
|
|
|
<div class="table-v2-demo">
|
|
|
|
|
<!-- Navigation anchors -->
|
|
|
|
|
<nav class="demo-nav">
|
|
|
|
|
<a v-for="section in sections" :key="section.id" :href="'#' + section.id" class="nav-link">
|
|
|
|
|
{{ section.label }}
|
|
|
|
|
</a>
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 1: Basic Usage
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[0].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">1. {{ sections[0].label }}</h2>
|
|
|
|
|
<p class="section-desc">Basic data binding with columns, pagination, and event handling.</p>
|
|
|
|
|
|
|
|
|
|
<div class="demo-toolbar">
|
|
|
|
|
<span class="test-indicator" :class="testResults.basic.events ? 'pass' : 'pending'">
|
|
|
|
|
@row-click: {{ testResults.basic.events ? "✓ fired" : "—" }}
|
|
|
|
|
</span>
|
|
|
|
|
<span class="test-indicator" :class="testResults.basic.cellClick ? 'pass' : 'pending'">
|
|
|
|
|
@cell-click: {{ testResults.basic.cellClick ? "✓ fired" : "—" }}
|
|
|
|
|
</span>
|
|
|
|
|
<span class="test-indicator" :class="testResults.basic.query ? 'pass' : 'pending'">
|
|
|
|
|
@query: {{ testResults.basic.query ? "✓ fired" : "—" }}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<SearchRow :title="t('table.title') + ' (V2) - Basic'">
|
|
|
|
|
<template #default>
|
|
|
|
|
<NoobInput v-model="basicExample.aaa" :width="180" placeholder="Filter case name"></NoobInput>
|
|
|
|
|
</template>
|
|
|
|
|
</SearchRow>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="basicData"
|
|
|
|
|
:columns="basicColumns"
|
|
|
|
|
:page="true"
|
|
|
|
|
:border="true"
|
|
|
|
|
:example="basicExample"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
height="350"
|
|
|
|
|
@query="handleBasicQuery"
|
|
|
|
|
@row-click="handleBasicRowClick"
|
|
|
|
|
@cell-click="handleBasicCellClick"
|
|
|
|
|
debug
|
|
|
|
|
></ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>// Columns definition
|
|
|
|
|
const basicColumns = [
|
|
|
|
|
{ key: 'id', name: 'ID' },
|
|
|
|
|
{ key: 'caseName', name: 'Case Name' },
|
|
|
|
|
{ key: 'taskName', name: 'Task Name' },
|
|
|
|
|
{ key: 'userId', name: 'User ID' },
|
|
|
|
|
{ key: 'createTime', name: 'Create Time', timestamp: 'unix' },
|
|
|
|
|
];</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 2: Styling - Borders & Alignment
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[1].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">2. {{ sections[1].label }}</h2>
|
|
|
|
|
<p class="section-desc">
|
|
|
|
|
Table borders and column content alignment via <code>border</code> and <code>align</code> props.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="demo-toolbar">
|
|
|
|
|
<el-button size="small" @click="toggleBorder">{{ borderDemo.border ? "Remove" : "Add" }} Borders</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<SearchRow title="Styling Demo">
|
|
|
|
|
<template #default>
|
|
|
|
|
<NoobSelect v-model="borderDemo.align" dict="test" :width="120"></NoobSelect>
|
|
|
|
|
</template>
|
|
|
|
|
</SearchRow>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="styleData"
|
|
|
|
|
:columns="styleColumns"
|
|
|
|
|
:page="false"
|
|
|
|
|
:border="borderDemo.border"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
height="350"
|
|
|
|
|
debug
|
|
|
|
|
></ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>// Alignment options: 'left' | 'center' | 'right'
|
|
|
|
|
const styleColumns = [
|
|
|
|
|
{ key: 'id', name: 'ID', align: 'center' },
|
|
|
|
|
{ key: 'caseName', name: 'Case Name', align: 'left' },
|
|
|
|
|
{ key: 'userId', name: 'User ID', align: 'right' },
|
|
|
|
|
];</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 3: Fixed Columns
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[2].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">3. {{ sections[2].label }}</h2>
|
|
|
|
|
<p class="section-desc">
|
|
|
|
|
Fix columns to left or right using <code>fixed: "left"</code> or <code>fixed: "right"</code>.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="demo-toolbar">
|
|
|
|
|
<span class="test-indicator" :class="testResults.fixedScroll ? 'pass' : 'pending'">
|
|
|
|
|
Scroll test: {{ testResults.fixedScroll ? "✓" : "—" }}
|
|
|
|
|
</span>
|
|
|
|
|
<span class="test-indicator">← ID and Case Name fixed left | Actions fixed right →</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="fixedData"
|
|
|
|
|
:columns="fixedColumns"
|
|
|
|
|
:page="true"
|
|
|
|
|
:border="true"
|
|
|
|
|
:example="fixedExample"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
height="350"
|
|
|
|
|
@query="handleFixedQuery"
|
|
|
|
|
debug
|
|
|
|
|
></ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>const fixedColumns = [
|
|
|
|
|
{ key: 'id', name: 'ID', fixed: 'left', width: 80 },
|
|
|
|
|
{ key: 'caseName', name: 'Case Name', fixed: 'left', width: 200 },
|
|
|
|
|
{ key: 'taskName', name: 'Task Name' },
|
|
|
|
|
{ key: 'userId', name: 'User ID' },
|
|
|
|
|
{ key: 'content', name: 'Content' },
|
|
|
|
|
{ key: 'createTime', name: 'Create Time', timestamp: 'unix' },
|
|
|
|
|
{ key: 'actions', name: 'Actions', fixed: 'right', width: 120 },
|
|
|
|
|
];</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 4: Data Formatting
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[3].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">4. {{ sections[3].label }}</h2>
|
|
|
|
|
<p class="section-desc">Built-in formatting for timestamps, dictionaries, and file sizes.</p>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="formatData"
|
|
|
|
|
:columns="formatColumns"
|
|
|
|
|
:page="false"
|
|
|
|
|
:border="true"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
height="350"
|
|
|
|
|
debug
|
|
|
|
|
></ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>const formatColumns = [
|
|
|
|
|
{ key: 'id', name: 'ID' },
|
|
|
|
|
{ key: 'caseName', name: 'Case Name' },
|
|
|
|
|
// timestamp: 'unix' formats Unix timestamp (seconds)
|
|
|
|
|
{ key: 'createTime', name: 'Create Time', timestamp: 'unix' },
|
|
|
|
|
// filesize: true formats bytes to K/M/G
|
|
|
|
|
{ key: 'fileSize', name: 'File Size', filesize: true },
|
|
|
|
|
// dict uses state.dict for lookup
|
|
|
|
|
{ key: 'status', name: 'Status', dict: 'test' },
|
|
|
|
|
];</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 5: Custom Cell Renderer
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[4].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">5. {{ sections[4].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.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="demo-toolbar">
|
|
|
|
|
<span class="test-indicator" :class="testResults.cellRenderer ? 'pass' : 'pending'">
|
|
|
|
|
Custom renderer: {{ testResults.cellRenderer ? "✓ rendered" : "—" }}
|
|
|
|
|
</span>
|
|
|
|
|
<span class="test-indicator" :class="testResults.cellRendererMini ? 'pass' : 'pending'">
|
|
|
|
|
Mini-table sync: {{ testResults.cellRendererMini ? "✓" : "—" }}
|
|
|
|
|
</span>
|
|
|
|
|
<el-button size="small" @click="testCellRendererResize">Test Resize Probe</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="rendererData"
|
|
|
|
|
:columns="rendererColumns"
|
|
|
|
|
:page="false"
|
|
|
|
|
:border="true"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
height="350"
|
|
|
|
|
debug
|
|
|
|
|
></ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>import { CellRenderer, CellRendererParams } from 'element-plus';
|
|
|
|
|
import { ElTag } from 'element-plus';
|
|
|
|
|
|
|
|
|
|
// Custom cellRenderer returning JSX
|
|
|
|
|
const statusRenderer: CellRenderer<T> = ({ cellData }) => {
|
|
|
|
|
const status = cellData as string;
|
|
|
|
|
const type = status === 'active' ? 'success' : status === 'pending' ? 'warning' : 'info';
|
|
|
|
|
return <ElTag type={type}>{status}</ElTag>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const rendererColumns = [
|
|
|
|
|
{ key: 'id', name: 'ID' },
|
|
|
|
|
{ key: 'caseName', name: 'Case Name' },
|
|
|
|
|
{ key: 'status', name: 'Status', cellRenderer: statusRenderer },
|
|
|
|
|
{ key: 'actions', name: 'Actions', cellRenderer: actionRenderer },
|
|
|
|
|
];</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 6: Custom Header Renderer
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[5].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">6. {{ sections[5].label }}</h2>
|
|
|
|
|
<p class="section-desc">
|
|
|
|
|
Custom header content via <code>headerCellRenderer</code> function. Must also sync with mini-table header probe.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="demo-toolbar">
|
|
|
|
|
<span class="test-indicator" :class="testResults.headerRenderer ? 'pass' : 'pending'">
|
|
|
|
|
Header renderer: {{ testResults.headerRenderer ? "✓ rendered" : "—" }}
|
|
|
|
|
</span>
|
|
|
|
|
<span class="test-indicator" :class="testResults.headerRendererMini ? 'pass' : 'pending'">
|
|
|
|
|
Mini-table header sync: {{ testResults.headerRendererMini ? "✓" : "—" }}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="headerData"
|
|
|
|
|
:columns="headerColumns"
|
|
|
|
|
:page="false"
|
|
|
|
|
:border="true"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
height="350"
|
|
|
|
|
debug
|
|
|
|
|
></ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>import { HeaderCellRenderer, HeaderCellRendererParams } from 'element-plus';
|
|
|
|
|
|
|
|
|
|
// Custom headerCellRenderer returning JSX
|
|
|
|
|
const headerRenderer: HeaderCellRenderer<T> = ({ column }) => {
|
|
|
|
|
const col: ListTableColumn<T> = column._listTableColumn;
|
|
|
|
|
return (
|
|
|
|
|
<span>
|
|
|
|
|
<span style="color: var(--el-color-primary)">*</span> {col.title}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const headerColumns = [
|
|
|
|
|
{ key: 'id', name: 'ID', headerCellRenderer },
|
|
|
|
|
{ key: 'caseName', name: 'Case Name', headerCellRenderer },
|
|
|
|
|
{ key: 'taskName', name: 'Task Name', headerCellRenderer },
|
|
|
|
|
];</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 7: Dynamic Height (Auto Probe)
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[6].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">7. {{ sections[6].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 —
|
|
|
|
|
it measures automatically.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="demo-toolbar">
|
|
|
|
|
<span class="test-indicator" :class="testResults.dynamicHeight ? 'pass' : 'pending'">
|
|
|
|
|
Height probed: {{ testResults.dynamicHeight ? "✓" : "—" }}
|
|
|
|
|
</span>
|
|
|
|
|
<el-button size="small" @click="testResize">Trigger ResizeObserver</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="dynamicData"
|
|
|
|
|
:columns="dynamicColumns"
|
|
|
|
|
:page="true"
|
|
|
|
|
:border="true"
|
|
|
|
|
:example="dynamicExample"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
height="350"
|
|
|
|
|
debug
|
|
|
|
|
></ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>// Dynamic height mode (no rowHeight set)
|
|
|
|
|
// Mini-table probe automatically measures row height
|
|
|
|
|
// ResizeObserver debounces at 50ms to prevent flicker
|
|
|
|
|
|
|
|
|
|
// Fixed height mode (rowHeight set) - disables probe
|
|
|
|
|
// <ListTableV2 :row-height="60" ...> // No probe needed
|
|
|
|
|
|
|
|
|
|
// Manual estimate mode
|
|
|
|
|
// <ListTableV2 :estimated-row-height="50" ...> // Uses this as starting estimate</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 8: Fixed Height Mode
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[7].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">8. {{ sections[7].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.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="demo-toolbar">
|
|
|
|
|
<span class="test-indicator">rowHeight: 60 | No probe active</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="fixedHeightData"
|
|
|
|
|
:columns="fixedHeightColumns"
|
|
|
|
|
:page="false"
|
|
|
|
|
:border="true"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
:row-height="60"
|
|
|
|
|
height="350"
|
|
|
|
|
debug
|
|
|
|
|
></ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>// Fixed row height - no mini-table probe needed
|
|
|
|
|
// Better performance for large datasets
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="data"
|
|
|
|
|
:columns="columns"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
:row-height="60"
|
|
|
|
|
:border="true"
|
|
|
|
|
debug
|
|
|
|
|
/></code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 9: Column Width Distribution
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[8].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">9. {{ sections[8].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.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="widthData"
|
|
|
|
|
:columns="widthColumns"
|
|
|
|
|
:page="false"
|
|
|
|
|
:border="true"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
height="350"
|
|
|
|
|
debug
|
|
|
|
|
></ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>const widthColumns = [
|
|
|
|
|
{ key: 'id', name: 'ID', width: 80 }, // Fixed 80px
|
|
|
|
|
{ key: 'caseName', name: 'Case Name', minWidth: 200 }, // Min 200px, grows
|
|
|
|
|
{ key: 'taskName', name: 'Task Name', minWidth: 150 }, // Min 150px, grows
|
|
|
|
|
{ key: 'userId', name: 'User ID', width: 120 }, // Fixed 120px
|
|
|
|
|
// Remaining space distributed proportionally
|
|
|
|
|
];</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 10: Width + Height + MaxHeight
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[9].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">10. {{ sections[9].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.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="demo-toolbar">
|
|
|
|
|
<el-button size="small" @click="toggleMaxHeight">{{ showMaxHeight ? "Remove" : "Add" }} Max Height</el-button>
|
|
|
|
|
<el-button size="small" @click="cycleHeaderHeight">Cycle Header Height</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="dimensionData"
|
|
|
|
|
:columns="dimensionColumns"
|
|
|
|
|
:page="false"
|
|
|
|
|
:border="true"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
:max-height="showMaxHeight ? 300 : undefined"
|
|
|
|
|
:header-height="dimensionHeaderHeight"
|
|
|
|
|
height="350"
|
|
|
|
|
debug
|
|
|
|
|
></ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>// Fixed height with scrolling internally
|
|
|
|
|
<ListTableV2 :height="400" ...>
|
|
|
|
|
|
|
|
|
|
// Max height - table grows but caps at max
|
|
|
|
|
<ListTableV2 :max-height="300" ...>
|
|
|
|
|
|
|
|
|
|
// Custom header height (default is auto-probed)
|
|
|
|
|
<ListTableV2 :header-height="60" ...></code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 11: Slot-based Custom Cells
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[10].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">11. {{ sections[10].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.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="demo-toolbar">
|
|
|
|
|
<span class="test-indicator" :class="testResults.slotCells ? 'pass' : 'pending'">
|
|
|
|
|
Slot cells: {{ testResults.slotCells ? "✓ rendered" : "—" }}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="slotData"
|
|
|
|
|
:columns="slotColumns"
|
|
|
|
|
:page="false"
|
|
|
|
|
:border="true"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
height="350"
|
|
|
|
|
debug
|
|
|
|
|
>
|
|
|
|
|
<template #status="{ row }">
|
|
|
|
|
<el-tag :type="row?.status === 'active' ? 'success' : 'warning'" size="small">
|
|
|
|
|
{{ row?.status }}
|
|
|
|
|
</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>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>// In template
|
|
|
|
|
<ListTableV2 :columns="slotColumns" ...>
|
|
|
|
|
<template #status="{ row }">
|
|
|
|
|
<el-tag>{row.status}</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
<template #actions="{ row }">
|
|
|
|
|
<el-button>Edit</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</ListTableV2>
|
|
|
|
|
|
|
|
|
|
// In script
|
|
|
|
|
const slotColumns = [
|
|
|
|
|
{ key: 'id', name: 'ID', slot: true },
|
|
|
|
|
{ key: 'caseName', name: 'Case Name' },
|
|
|
|
|
{ key: 'status', name: 'Status', slot: true },
|
|
|
|
|
{ key: 'actions', name: 'Actions', slot: true },
|
|
|
|
|
];</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 12: i18n Support
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[11].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">12. {{ sections[11].label }}</h2>
|
|
|
|
|
<p class="section-desc">Column headers support i18n via <code>i18n</code> key (uses <code>vue3-i18n</code>).</p>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="i18nData"
|
|
|
|
|
:columns="i18nColumns"
|
|
|
|
|
:page="false"
|
|
|
|
|
:border="true"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
height="350"
|
|
|
|
|
debug
|
|
|
|
|
></ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>// The component uses t() from vue3-i18n internally
|
|
|
|
|
// Column with i18n key:
|
|
|
|
|
const i18nColumns = [
|
|
|
|
|
{ key: 'id', name: 'ID' },
|
|
|
|
|
{ key: 'caseName', i18n: 'table.props.0' }, // Uses t('table.props.0')
|
|
|
|
|
{ key: 'taskName', i18n: 'table.props.1' },
|
|
|
|
|
];</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 13: Combined Features Test
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[12].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">13. {{ sections[12].label }}</h2>
|
|
|
|
|
<p class="section-desc">
|
|
|
|
|
All features working together: fixed columns + custom renderers + pagination + dynamic height probe + events.
|
|
|
|
|
This is the stress test.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="demo-toolbar">
|
|
|
|
|
<el-button size="small" type="primary" @click="runCombinedTest">Run Combined Test</el-button>
|
|
|
|
|
<el-button size="small" @click="reloadCombinedData">Reload Data</el-button>
|
|
|
|
|
<span v-for="(v, k) in testResults.combined" :key="k" class="test-indicator" :class="v ? 'pass' : 'pending'">
|
|
|
|
|
{{ k }}: {{ v ? "✓" : "✗" }}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="combinedData"
|
|
|
|
|
:columns="combinedColumns"
|
|
|
|
|
:page="true"
|
|
|
|
|
:border="true"
|
|
|
|
|
:example="combinedExample"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
height="350"
|
|
|
|
|
@query="handleCombinedQuery"
|
|
|
|
|
@row-click="handleCombinedRowClick"
|
|
|
|
|
@cell-click="handleCombinedCellClick"
|
|
|
|
|
debug
|
|
|
|
|
>
|
|
|
|
|
<template #combinedStatus="{ row }">
|
|
|
|
|
<el-tag :type="getStatusType(row?.status)" size="small">{{ row?.status }}</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
<template #combinedActions="{ row }">
|
|
|
|
|
<el-button link type="primary" size="small">View</el-button>
|
|
|
|
|
<el-button link type="warning" size="small">Edit</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>// Combined: fixed columns + custom renderers + pagination + i18n
|
|
|
|
|
const combinedColumns = [
|
|
|
|
|
{ key: 'id', name: 'ID', fixed: 'left', width: 80 },
|
|
|
|
|
{ key: 'caseName', i18n: 'table.props.0', fixed: 'left' },
|
|
|
|
|
{ key: 'taskName', i18n: 'table.props.1' },
|
|
|
|
|
{ key: 'userId', name: 'User', dict: 'test' },
|
|
|
|
|
{ key: 'createTime', name: 'Time', timestamp: 'unix', minWidth: 180 },
|
|
|
|
|
{ key: 'combinedStatus', name: 'Status', slot: true },
|
|
|
|
|
{ key: 'combinedActions', name: 'Actions', fixed: 'right', slot: true, width: 140 },
|
|
|
|
|
];</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ============================================================
|
|
|
|
|
SECTION 14: Custom Renderer + Fixed + Pagination
|
|
|
|
|
============================================================ -->
|
|
|
|
|
<section :id="sections[13].id" class="demo-section">
|
|
|
|
|
<h2 class="section-title">14. {{ sections[13].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.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="demo-toolbar">
|
|
|
|
|
<el-button size="small" @click="changePageRenderer">Change Page</el-button>
|
|
|
|
|
<el-button size="small" @click="toggleRendererType">Toggle Renderer Type</el-button>
|
|
|
|
|
<span class="test-indicator">Renderer type: {{ rendererType }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ListTableV2
|
|
|
|
|
:data="edgeData"
|
|
|
|
|
:columns="edgeColumns"
|
|
|
|
|
:page="true"
|
|
|
|
|
:border="true"
|
|
|
|
|
:example="edgeExample"
|
|
|
|
|
:row-key="'id'"
|
|
|
|
|
height="350"
|
|
|
|
|
@query="handleEdgeQuery"
|
|
|
|
|
debug
|
|
|
|
|
></ListTableV2>
|
|
|
|
|
|
|
|
|
|
<div class="demo-code">
|
|
|
|
|
<pre><code>// Edge case: cellRenderer + fixed columns + pagination
|
|
|
|
|
// Must verify mini-table probe renders custom JSX correctly
|
|
|
|
|
const edgeColumns = [
|
|
|
|
|
{ key: 'id', fixed: 'left', cellRenderer: edgeIdRenderer },
|
|
|
|
|
{ key: 'caseName', cellRenderer: edgeNameRenderer },
|
|
|
|
|
{ key: 'priority', cellRenderer: edgePriorityRenderer },
|
|
|
|
|
{ key: 'actions', fixed: 'right', cellRenderer: edgeActionsRenderer },
|
|
|
|
|
];</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<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 { useI18n } from "vue3-i18n";
|
|
|
|
|
import { LoremIpsum } from "lorem-ipsum";
|
|
|
|
|
import { ElTag, ElButton, ElLink } from "element-plus";
|
|
|
|
|
import {
|
|
|
|
|
CellRenderer,
|
|
|
|
|
CellRendererParams,
|
|
|
|
|
HeaderCellRenderer,
|
|
|
|
|
HeaderCellRendererParams,
|
|
|
|
|
} from "element-plus/es/components/table-v2/src/types.mjs";
|
|
|
|
|
import { type ListTableColumn } from "../../../packages/base/data/list-table-v2";
|
|
|
|
|
|
|
|
|
|
// --- Data Generator ---
|
|
|
|
|
const generateRows = (count: number) => {
|
|
|
|
|
const rows: Array<Record<string, any>> = [];
|
|
|
|
|
const now = Math.floor(Date.now() / 1000);
|
|
|
|
|
const lorem = new LoremIpsum({
|
|
|
|
|
sentencesPerParagraph: { min: 1, max: 3 },
|
|
|
|
|
wordsPerSentence: { min: 4, max: 12 },
|
|
|
|
|
});
|
|
|
|
|
for (let i = 1; i <= count; i++) {
|
|
|
|
|
rows.push({
|
|
|
|
|
id: i,
|
|
|
|
|
caseName: `案件${i}—${lorem.generateWords(3)}`,
|
|
|
|
|
taskName: `任务${i}`,
|
|
|
|
|
userId: `user${i % 10}`,
|
|
|
|
|
content: lorem.generateSentences(1),
|
|
|
|
|
createTime: now - i * 60 * ((i % 10) + 1),
|
|
|
|
|
fileSize: Math.floor(Math.random() * 10000000),
|
|
|
|
|
status: ["active", "pending", "inactive", "completed"][i % 4],
|
|
|
|
|
priority: ["high", "medium", "low"][i % 3],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return rows;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const allRows = generateRows(200);
|
|
|
|
|
|
|
|
|
|
// --- i18n ---
|
|
|
|
|
const { t } = useI18n();
|
|
|
|
|
|
|
|
|
|
// --- Section Navigation ---
|
|
|
|
|
const sections = [
|
|
|
|
|
{ id: "s1-basic", label: "Basic Usage" },
|
|
|
|
|
{ id: "s2-styling", label: "Styling" },
|
|
|
|
|
{ id: "s3-fixed", label: "Fixed Columns" },
|
|
|
|
|
{ id: "s4-formatting", label: "Data Formatting" },
|
|
|
|
|
{ id: "s5-cell-renderer", label: "Custom Cell Renderer" },
|
|
|
|
|
{ id: "s6-header-renderer", label: "Custom Header Renderer" },
|
|
|
|
|
{ id: "s7-dynamic-height", label: "Dynamic Height" },
|
|
|
|
|
{ id: "s8-fixed-height", label: "Fixed Height Mode" },
|
|
|
|
|
{ id: "s9-width-dist", label: "Column Width" },
|
|
|
|
|
{ id: "s10-dimensions", label: "Height/MaxHeight" },
|
|
|
|
|
{ id: "s11-slots", label: "Slot Cells" },
|
|
|
|
|
{ id: "s12-i18n", label: "i18n Support" },
|
|
|
|
|
{ id: "s13-combined", label: "Combined Test" },
|
|
|
|
|
{ id: "s14-edge-renderer", label: "Renderer Edge Cases" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// --- Test Results ---
|
|
|
|
|
const testResults = reactive<Record<string, any>>({
|
|
|
|
|
basic: { events: false, cellClick: false, query: false },
|
|
|
|
|
fixedScroll: false,
|
|
|
|
|
cellRenderer: false,
|
|
|
|
|
cellRendererMini: false,
|
|
|
|
|
headerRenderer: false,
|
|
|
|
|
headerRendererMini: false,
|
|
|
|
|
dynamicHeight: false,
|
|
|
|
|
slotCells: false,
|
|
|
|
|
combined: {},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 1: Basic
|
|
|
|
|
// ================================================================
|
|
|
|
|
const basicExample = reactive({ page: 1, size: 10, aaa: "" });
|
|
|
|
|
const basicData = reactive({ data: allRows.slice(0, 10), total: allRows.length });
|
|
|
|
|
const basicColumns = [
|
|
|
|
|
{ key: "id", name: "ID" },
|
|
|
|
|
{ key: "caseName", name: "Case Name" },
|
|
|
|
|
{ key: "taskName", name: "Task Name" },
|
|
|
|
|
{ key: "userId", name: "User ID" },
|
|
|
|
|
{ key: "createTime", name: "Create Time", timestamp: "unix" },
|
|
|
|
|
];
|
|
|
|
|
const handleBasicQuery = () => {
|
|
|
|
|
testResults.basic.query = true;
|
|
|
|
|
const start = (basicExample.page - 1) * basicExample.size;
|
|
|
|
|
basicData.data = allRows.slice(start, start + basicExample.size);
|
|
|
|
|
};
|
|
|
|
|
const handleBasicRowClick = (row: any) => {
|
|
|
|
|
testResults.basic.events = true;
|
|
|
|
|
console.log("row-click", row.id);
|
|
|
|
|
};
|
|
|
|
|
const handleBasicCellClick = (row: any) => {
|
|
|
|
|
testResults.basic.cellClick = true;
|
|
|
|
|
console.log("cell-click", row.id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 2: Styling
|
|
|
|
|
// ================================================================
|
|
|
|
|
const borderDemo = reactive({ border: true, align: "center" });
|
|
|
|
|
const styleData = allRows.slice(0, 20);
|
|
|
|
|
const styleColumns = computed(() => [
|
|
|
|
|
{ key: "id", name: "ID", align: "center" as const },
|
|
|
|
|
{ key: "caseName", name: "Case Name", align: "left" as const },
|
|
|
|
|
{ key: "userId", name: "User ID", align: "right" as const },
|
|
|
|
|
{ key: "createTime", name: "Create Time", timestamp: "unix", align: "center" as const },
|
|
|
|
|
]);
|
|
|
|
|
const toggleBorder = () => {
|
|
|
|
|
borderDemo.border = !borderDemo.border;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 3: Fixed Columns
|
|
|
|
|
// ================================================================
|
|
|
|
|
const fixedExample = reactive({ page: 1, size: 10 });
|
|
|
|
|
const fixedData = reactive({ data: allRows.slice(0, 10), total: allRows.length });
|
|
|
|
|
const fixedColumns = [
|
|
|
|
|
{ key: "id", name: "ID", fixed: "left" as const, width: 80 },
|
|
|
|
|
{ key: "caseName", name: "Case Name", fixed: "left" as const, width: 200 },
|
|
|
|
|
{ key: "taskName", name: "Task Name" },
|
|
|
|
|
{ key: "userId", name: "User ID" },
|
|
|
|
|
{ key: "content", name: "Content", minWidth: 200 },
|
|
|
|
|
{ key: "createTime", name: "Create Time", timestamp: "unix" },
|
|
|
|
|
{ key: "actions", name: "Actions", fixed: "right" as const, width: 120 },
|
|
|
|
|
];
|
|
|
|
|
const handleFixedQuery = () => {
|
|
|
|
|
const start = (fixedExample.page - 1) * fixedExample.size;
|
|
|
|
|
fixedData.data = allRows.slice(start, start + fixedExample.size);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 4: Formatting
|
|
|
|
|
// ================================================================
|
|
|
|
|
const formatData = allRows.slice(0, 20);
|
|
|
|
|
const formatColumns = [
|
|
|
|
|
{ key: "id", name: "ID" },
|
|
|
|
|
{ key: "caseName", name: "Case Name" },
|
|
|
|
|
{ key: "createTime", name: "Create Time (unix)", timestamp: "unix" as const },
|
|
|
|
|
{ key: "fileSize", name: "File Size", filesize: true as const },
|
|
|
|
|
{ key: "status", name: "Status (dict)", dict: "test" as const },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 5: Custom Cell Renderer
|
|
|
|
|
// ================================================================
|
|
|
|
|
const statusRenderer: CellRenderer<any> = ({ cellData }) => {
|
|
|
|
|
testResults.cellRenderer = true;
|
|
|
|
|
const status = cellData as string;
|
|
|
|
|
const type =
|
|
|
|
|
status === "active" ? "success" : status === "pending" ? "warning" : status === "inactive" ? "info" : "danger";
|
|
|
|
|
return (
|
|
|
|
|
<ElTag type={type} size="small">
|
|
|
|
|
{status}
|
|
|
|
|
</ElTag>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const actionRenderer: CellRenderer<any> = ({ rowData }) => {
|
|
|
|
|
return (
|
|
|
|
|
<div style="display: flex; gap: 4px;">
|
|
|
|
|
<ElButton size="small" link type="primary">
|
|
|
|
|
Edit
|
|
|
|
|
</ElButton>
|
|
|
|
|
<ElButton size="small" link type="danger">
|
|
|
|
|
Del
|
|
|
|
|
</ElButton>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const rendererData = ref(allRows.slice(0, 20));
|
|
|
|
|
const rendererColumns: ListTableColumn<any>[] = [
|
|
|
|
|
{ key: "id", name: "ID" },
|
|
|
|
|
{ key: "caseName", name: "Case Name" },
|
|
|
|
|
{ key: "status", name: "Status", cellRenderer: statusRenderer },
|
|
|
|
|
{
|
|
|
|
|
key: "priority",
|
|
|
|
|
name: "Priority",
|
|
|
|
|
cellRenderer: ({ cellData }) => {
|
|
|
|
|
const colors = { high: "danger" as const, medium: "warning" as const, low: "info" as const };
|
|
|
|
|
return (
|
|
|
|
|
<ElTag type={colors[cellData] || "info"} size="small">
|
|
|
|
|
{cellData}
|
|
|
|
|
</ElTag>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{ key: "actions", name: "Actions", cellRenderer: actionRenderer },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const testCellRendererResize = () => {
|
|
|
|
|
testResults.cellRendererMini = true;
|
|
|
|
|
// Force re-render by toggling data
|
|
|
|
|
rendererData.value = allRows.slice(0, 20);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 6: Custom Header Renderer
|
|
|
|
|
// ================================================================
|
|
|
|
|
const headerRenderer: HeaderCellRenderer<any> = ({ column }) => {
|
|
|
|
|
testResults.headerRenderer = true;
|
|
|
|
|
return (
|
|
|
|
|
<span>
|
|
|
|
|
<span style="color: var(--el-color-danger)">★</span> {(column as any).title}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const headerData = allRows.slice(0, 20);
|
|
|
|
|
const headerColumns: ListTableColumn<any>[] = [
|
|
|
|
|
{ key: "id", name: "ID", headerCellRenderer: headerRenderer },
|
|
|
|
|
{ key: "caseName", name: "Case Name", headerCellRenderer: headerRenderer },
|
|
|
|
|
{ key: "taskName", name: "Task Name", headerCellRenderer: headerRenderer },
|
|
|
|
|
{ key: "userId", name: "User ID", headerCellRenderer: headerRenderer },
|
|
|
|
|
{ key: "createTime", name: "Create Time", timestamp: "unix", headerCellRenderer: headerRenderer },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 7: Dynamic Height
|
|
|
|
|
// ================================================================
|
|
|
|
|
const dynamicExample = reactive({ page: 1, size: 10 });
|
|
|
|
|
const dynamicData = reactive({ data: allRows.slice(0, 10), total: allRows.length });
|
|
|
|
|
const dynamicColumns = [
|
|
|
|
|
{ key: "id", name: "ID" },
|
|
|
|
|
{ key: "caseName", name: "Case Name" },
|
|
|
|
|
{ key: "content", name: "Content (long text to test height probe)" },
|
|
|
|
|
{ key: "taskName", name: "Task Name" },
|
|
|
|
|
{ key: "createTime", name: "Create Time", timestamp: "unix" },
|
|
|
|
|
];
|
|
|
|
|
const handleDynamicQuery = () => {
|
|
|
|
|
const start = (dynamicExample.page - 1) * dynamicExample.size;
|
|
|
|
|
dynamicData.data = allRows.slice(start, start + dynamicExample.size);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const testResize = () => {
|
|
|
|
|
dynamicData.data = allRows.slice(0, 10);
|
|
|
|
|
testResults.dynamicHeight = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 8: Fixed Height
|
|
|
|
|
// ================================================================
|
|
|
|
|
const fixedHeightData = allRows.slice(0, 50);
|
|
|
|
|
const fixedHeightColumns = [
|
|
|
|
|
{ key: "id", name: "ID" },
|
|
|
|
|
{ key: "caseName", name: "Case Name" },
|
|
|
|
|
{ key: "taskName", name: "Task Name" },
|
|
|
|
|
{ key: "userId", name: "User ID" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 9: Width Distribution
|
|
|
|
|
// ================================================================
|
|
|
|
|
const widthData = allRows.slice(0, 20);
|
|
|
|
|
const widthColumns = [
|
|
|
|
|
{ key: "id", name: "ID", width: 80 },
|
|
|
|
|
{ key: "caseName", name: "Case Name (min 200)", minWidth: 200 },
|
|
|
|
|
{ key: "taskName", name: "Task Name (min 150)", minWidth: 150 },
|
|
|
|
|
{ key: "userId", name: "User ID (fixed 120)", width: 120 },
|
|
|
|
|
{ key: "createTime", name: "Create Time", timestamp: "unix" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 10: Dimensions
|
|
|
|
|
// ================================================================
|
|
|
|
|
const showMaxHeight = ref(false);
|
|
|
|
|
const toggleMaxHeight = () => {
|
|
|
|
|
showMaxHeight.value = !showMaxHeight.value;
|
|
|
|
|
};
|
|
|
|
|
const dimensionHeaderHeight = ref<number | undefined>(undefined);
|
|
|
|
|
const dimensionData = allRows.slice(0, 30);
|
|
|
|
|
const dimensionColumns = [
|
|
|
|
|
{ key: "id", name: "ID" },
|
|
|
|
|
{ key: "caseName", name: "Case Name" },
|
|
|
|
|
{ key: "taskName", name: "Task Name" },
|
|
|
|
|
{ key: "userId", name: "User ID" },
|
|
|
|
|
{ key: "createTime", name: "Create Time", timestamp: "unix" },
|
|
|
|
|
];
|
|
|
|
|
const cycleHeaderHeight = () => {
|
|
|
|
|
const heights = [undefined, 40, 60, 80];
|
|
|
|
|
const idx = heights.indexOf(dimensionHeaderHeight.value);
|
|
|
|
|
dimensionHeaderHeight.value = heights[(idx + 1) % heights.length];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 11: Slot Cells
|
|
|
|
|
// ================================================================
|
|
|
|
|
const slotData = allRows.slice(0, 20);
|
|
|
|
|
const slotColumns = [
|
|
|
|
|
{ key: "id", name: "ID", slot: true },
|
|
|
|
|
{ key: "caseName", name: "Case Name" },
|
|
|
|
|
{ key: "status", name: "Status", slot: true },
|
|
|
|
|
{ key: "priority", name: "Priority", slot: true },
|
|
|
|
|
{ key: "actions", name: "Actions", slot: true },
|
|
|
|
|
];
|
|
|
|
|
const onSlotAction = (row: any) => {
|
|
|
|
|
testResults.slotCells = true;
|
|
|
|
|
console.log("Slot action:", row.id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 12: i18n
|
|
|
|
|
// ================================================================
|
|
|
|
|
const i18nData = allRows.slice(0, 20);
|
|
|
|
|
const i18nColumns = [
|
|
|
|
|
{ key: "id", name: "ID" },
|
|
|
|
|
{ key: "caseName", i18n: "table.props.0" },
|
|
|
|
|
{ key: "taskName", i18n: "table.props.1" },
|
|
|
|
|
{ key: "userId", i18n: "table.props.2" },
|
|
|
|
|
{ key: "content", i18n: "table.props.3" },
|
|
|
|
|
{ key: "createTime", i18n: "table.props.4", timestamp: "unix" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 13: Combined
|
|
|
|
|
// ================================================================
|
|
|
|
|
const combinedExample = reactive({ page: 1, size: 10 });
|
|
|
|
|
const combinedData = reactive({ data: allRows.slice(0, 10), total: allRows.length });
|
|
|
|
|
const combinedColumns = [
|
|
|
|
|
{ key: "id", name: "ID", fixed: "left" as const, width: 80 },
|
|
|
|
|
{ key: "caseName", i18n: "table.props.0", fixed: "left" as const },
|
|
|
|
|
{ key: "taskName", i18n: "table.props.1" },
|
|
|
|
|
{ key: "userId", i18n: "table.props.2", dict: "test" as const },
|
|
|
|
|
{ key: "createTime", name: "Create Time", timestamp: "unix" as const, minWidth: 180 },
|
|
|
|
|
{ key: "combinedStatus", name: "Status", slot: true },
|
|
|
|
|
{ key: "combinedActions", name: "Actions", fixed: "right" as const, slot: true, width: 140 },
|
|
|
|
|
];
|
|
|
|
|
const getStatusType = (status: string) => {
|
|
|
|
|
const map: Record<string, any> = { active: "success", pending: "warning", inactive: "info", completed: "success" };
|
|
|
|
|
return map[status] || "info";
|
|
|
|
|
};
|
|
|
|
|
const handleCombinedQuery = () => {
|
|
|
|
|
const start = (combinedExample.page - 1) * combinedExample.size;
|
|
|
|
|
combinedData.data = allRows.slice(start, start + combinedExample.size);
|
|
|
|
|
testResults.combined.query = true;
|
|
|
|
|
};
|
|
|
|
|
const handleCombinedRowClick = () => {
|
|
|
|
|
testResults.combined.rowClick = true;
|
|
|
|
|
};
|
|
|
|
|
const handleCombinedCellClick = () => {
|
|
|
|
|
testResults.combined.cellClick = true;
|
|
|
|
|
};
|
|
|
|
|
const runCombinedTest = () => {
|
|
|
|
|
testResults.combined = { query: false, rowClick: false, cellClick: false, renderer: false };
|
|
|
|
|
combinedExample.page = 1;
|
|
|
|
|
handleCombinedQuery();
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
combinedData.data = allRows.slice(0, 10);
|
|
|
|
|
testResults.combined.renderer = true;
|
|
|
|
|
}, 100);
|
|
|
|
|
};
|
|
|
|
|
const reloadCombinedData = () => {
|
|
|
|
|
combinedData.data = allRows.slice(0, 10);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// SECTION 14: Renderer Edge Cases
|
|
|
|
|
// ================================================================
|
|
|
|
|
const rendererType = ref<"jsx" | "simple">("jsx");
|
|
|
|
|
const edgeExample = reactive({ page: 1, size: 10 });
|
|
|
|
|
const edgeData = reactive({ data: allRows.slice(0, 10), total: allRows.length });
|
|
|
|
|
|
|
|
|
|
const edgeIdRenderer: CellRenderer<any> = ({ cellData }) => {
|
|
|
|
|
return <span style="font-weight: bold; color: var(--el-color-primary)">#{cellData}</span>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const edgeNameRenderer: CellRenderer<any> = ({ cellData }) => {
|
|
|
|
|
if (rendererType.value === "jsx") {
|
|
|
|
|
return <span style="font-style: italic">{cellData}</span>;
|
|
|
|
|
}
|
|
|
|
|
return <span>{cellData}</span>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const edgePriorityRenderer: CellRenderer<any> = ({ cellData }) => {
|
|
|
|
|
const colors = { high: "danger" as const, medium: "warning" as const, low: "success" as const };
|
|
|
|
|
return (
|
|
|
|
|
<ElTag type={colors[cellData]} size="small">
|
|
|
|
|
{cellData}
|
|
|
|
|
</ElTag>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const edgeActionsRenderer: CellRenderer<any> = () => {
|
|
|
|
|
return (
|
|
|
|
|
<div style="display: flex; gap: 4px;">
|
|
|
|
|
<ElButton size="small" type="primary">
|
|
|
|
|
Open
|
|
|
|
|
</ElButton>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const edgeColumns: ListTableColumn<any>[] = [
|
|
|
|
|
{ key: "id", name: "ID", fixed: "left" as const, width: 80, cellRenderer: edgeIdRenderer },
|
|
|
|
|
{ key: "caseName", name: "Case Name", cellRenderer: edgeNameRenderer },
|
|
|
|
|
{ key: "priority", name: "Priority", cellRenderer: edgePriorityRenderer },
|
|
|
|
|
{ key: "userId", name: "User ID" },
|
|
|
|
|
{ key: "createTime", name: "Create Time", timestamp: "unix" },
|
|
|
|
|
{ key: "actions", name: "Actions", fixed: "right" as const, width: 120, cellRenderer: edgeActionsRenderer },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const handleEdgeQuery = () => {
|
|
|
|
|
const start = (edgeExample.page - 1) * edgeExample.size;
|
|
|
|
|
edgeData.data = allRows.slice(start, start + edgeExample.size);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const changePageRenderer = () => {
|
|
|
|
|
edgeExample.page = (edgeExample.page % 5) + 1;
|
|
|
|
|
handleEdgeQuery();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toggleRendererType = () => {
|
|
|
|
|
rendererType.value = rendererType.value === "jsx" ? "simple" : "jsx";
|
|
|
|
|
edgeData.data = allRows.slice(0, 10);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
console.log("Table V2 Demo mounted");
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.table-v2-demo {
|
|
|
|
|
padding: 0;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.demo-nav {
|
|
|
|
|
position: sticky;
|
|
|
|
|
top: 0;
|
|
|
|
|
z-index: 100;
|
|
|
|
|
background: var(--el-fill-color-light);
|
|
|
|
|
border-bottom: 1px solid var(--el-border-color);
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
max-height: 120px;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-link {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: var(--el-color-primary);
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
padding: 2px 8px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
background: var(--el-color-primary-light-9);
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.demo-section {
|
|
|
|
|
padding: 24px 16px;
|
|
|
|
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
|
|
|
|
|
|
|
|
&:nth-child(even) {
|
|
|
|
|
background: var(--el-fill-color-lighter);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
color: var(--el-text-color-primary);
|
|
|
|
|
|
|
|
|
|
code {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
background: var(--el-fill-color);
|
|
|
|
|
padding: 2px 6px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
color: var(--el-color-primary);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-desc {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
margin: 0 0 16px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.demo-toolbar {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.test-indicator {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
padding: 2px 8px;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
background: var(--el-fill-color);
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
border: 1px solid var(--el-border-color-light);
|
|
|
|
|
|
|
|
|
|
&.pass {
|
|
|
|
|
background: var(--el-color-success-light-9);
|
|
|
|
|
color: var(--el-color-success);
|
|
|
|
|
border-color: var(--el-color-success-light-7);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.fail {
|
|
|
|
|
background: var(--el-color-danger-light-9);
|
|
|
|
|
color: var(--el-color-danger);
|
|
|
|
|
border-color: var(--el-color-danger-light-7);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.pending {
|
|
|
|
|
background: var(--el-fill-color);
|
|
|
|
|
color: var(--el-text-color-placeholder);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.demo-code {
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
background: var(--el-fill-color-dark);
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
|
|
|
|
pre {
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
code {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-family: "Fira Code", "Consolas", monospace;
|
|
|
|
|
color: var(--el-text-color-primary);
|
|
|
|
|
white-space: pre;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|